Merge commit '23d77afb55650c59e5cf224c33c0fbc1cfee76fb' into dev-release
diff --git a/build.gradle b/build.gradle
index 92e940a..8d1eb6b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -610,6 +610,7 @@
         options.errorprone.enabled = true
         options.errorprone.disableAllChecks = true
         options.errorprone.check('ClassCanBeStatic', CheckSeverity.ERROR)
+        options.errorprone.check('CollectionIncompatibleType', CheckSeverity.ERROR)
         options.errorprone.check('OperatorPrecedence', CheckSeverity.ERROR)
         options.errorprone.check('RemoveUnusedImports', CheckSeverity.ERROR)
         options.errorprone.check('MissingOverride', CheckSeverity.ERROR)
@@ -695,25 +696,25 @@
     }
 }
 
-task repackageDeps(type: ShadowJar) {
+task repackageDepsNew(type: ShadowJar) {
     configurations = [project.configurations.runtimeClasspath]
     mergeServiceFiles(it)
     exclude { it.getRelativePath().getPathString() == "module-info.class" }
     exclude { it.getRelativePath().getPathString().startsWith("META-INF/maven/") }
-    baseName 'deps'
+    baseName 'deps_all'
 }
 
-task repackageSources(type: ShadowJar) {
+task repackageSourcesNew(type: ShadowJar) {
     from sourceSets.main.output
     mergeServiceFiles(it)
-    baseName 'sources'
+    baseName 'sources_main'
 }
 
-task repackageSources11(type: ShadowJar) {
+task repackageSources11New(type: ShadowJar) {
     dependsOn compileMainWithJava11
     from file(java11ClassFiles)
     mergeServiceFiles(it)
-    baseName 'sources11'
+    baseName 'sources_main_11'
 }
 
 def r8CreateTask(name, baseNameName, sources, includeSwissArmyKnife) {
@@ -771,24 +772,26 @@
 }
 
 task r8WithDeps {
-    dependsOn repackageSources
-    dependsOn repackageDeps
+    dependsOn repackageSourcesNew
+    dependsOn repackageDepsNew
+    inputs.files ([repackageSourcesNew.outputs, repackageDepsNew.outputs])
     def r8Task = r8CreateTask(
             'WithDeps',
             'r8_with_deps',
-            repackageSources.outputs.files + repackageDeps.outputs.files,
+            repackageSourcesNew.outputs.files + repackageDepsNew.outputs.files,
             true)
     dependsOn r8Task
     outputs.files r8Task.outputs.files
 }
 
 task r8WithDeps11 {
-    dependsOn repackageSources11
-    dependsOn repackageDeps
+    dependsOn repackageSources11New
+    dependsOn repackageDepsNew
+    inputs.files ([repackageSources11New.outputs, repackageDepsNew.outputs])
     def r8Task = r8CreateTask(
             'WithDeps11',
             'r8_with_deps_11',
-            repackageSources11.outputs.files + repackageDeps.outputs.files,
+            repackageSources11New.outputs.files + repackageDepsNew.outputs.files,
             true)
     dependsOn r8Task
     outputs.files r8Task.outputs.files
@@ -797,21 +800,24 @@
 task r8WithRelocatedDeps {
     def output = "${buildDir}/libs/r8_with_relocated_deps.jar"
     dependsOn r8RelocateTask(r8WithDeps, output)
+    inputs.files r8WithDeps.outputs.files
     outputs.file output
 }
 
 task r8WithRelocatedDeps11 {
     def output = "${buildDir}/libs/r8_with_relocated_deps_11.jar"
     dependsOn r8RelocateTask(r8WithDeps11, output)
+    inputs.files r8WithDeps11.outputs.files
     outputs.file output
 }
 
 task r8WithoutDeps {
-    dependsOn repackageSources
+    dependsOn repackageSourcesNew
+    inputs.files repackageSourcesNew.outputs
     def r8Task = r8CreateTask(
             'WithoutDeps',
             'r8_without_deps',
-            repackageSources.outputs.files,
+            repackageSourcesNew.outputs.files,
             true)
     dependsOn r8Task
     outputs.files r8Task.outputs.files
@@ -828,22 +834,24 @@
 }
 
 task r8NoManifestWithoutDeps {
-    dependsOn repackageSources
+    dependsOn repackageSourcesNew
+    inputs.files repackageSourcesNew.outputs
     def r8Task = r8CreateTask(
             'NoManifestWithoutDeps',
             'r8_no_manifest_without_deps',
-            repackageSources.outputs.files,
+            repackageSourcesNew.outputs.files,
             false)
     dependsOn r8Task
     outputs.files r8Task.outputs.files
 }
 
 task r8NoManifestWithDeps {
-    dependsOn repackageSources
+    dependsOn repackageSourcesNew
+    inputs.files ([repackageSourcesNew.outputs, repackageDepsNew.outputs])
     def r8Task = r8CreateTask(
             'NoManifestWithDeps',
             'r8_no_manifest_with_deps',
-            repackageSources.outputs.files + repackageDeps.outputs.files,
+            repackageSourcesNew.outputs.files + repackageDepsNew.outputs.files,
             false)
     dependsOn r8Task
     outputs.files r8Task.outputs.files
@@ -852,6 +860,7 @@
 task r8NoManifestWithRelocatedDeps {
     def output = "${buildDir}/libs/r8_no_manifest_with_relocated_deps.jar"
     dependsOn r8RelocateTask(r8NoManifestWithDeps, output)
+    inputs.files r8NoManifestWithDeps.outputs.files
     outputs.file output
 }
 
@@ -925,7 +934,7 @@
     def output = "$buildDir/libs/r8tests.jar"
     outputs.file output
     workingDir = projectDir
-    inputs.files ([testJarSources.outputs, r8WithDeps.outputs])
+    inputs.files (testJarSources.outputs.files + r8WithDeps.outputs.files)
     commandLine baseR8CommandLine([
             "relocator",
             "--input",
@@ -942,20 +951,21 @@
         // TODO(b/154785341): We should remove this.
         standardOutput new FileOutputStream(r8LibGeneratedKeepRulesPath)
     }
-    dependsOn r8WithRelocatedDeps
-    dependsOn r8NoManifestWithDeps
+    // Depend on r8WithDeps for running baseR8CommandLine.
+    dependsOn r8WithDeps
+    dependsOn r8NoManifestWithRelocatedDeps
     dependsOn testJar
     dependsOn downloadOpenJDKrt
     inputs.files ([
-            r8WithRelocatedDeps.outputs,
-            r8NoManifestWithDeps.outputs,
+            r8WithDeps.outputs,
+            r8NoManifestWithRelocatedDeps.outputs,
             testJar.outputs])
     outputs.file r8LibGeneratedKeepRulesPath
     commandLine baseR8CommandLine([
             "printuses",
             "--keeprules-allowobfuscation",
             "third_party/openjdk/openjdk-rt-1.8/rt.jar",
-            r8NoManifestWithDeps.outputs.files[0],
+            r8NoManifestWithRelocatedDeps.outputs.files[0],
             testJar.outputs.files[0]])
     workingDir = projectDir
 }
@@ -974,6 +984,7 @@
             r8NoManifestWithRelocatedDeps,
             r8LibPath,
     ).dependsOn(generateR8LibKeepRules)
+    inputs.files r8NoManifestWithRelocatedDeps.outputs.files
     outputs.file r8LibPath
 }
 
@@ -984,8 +995,9 @@
             r8NoManifestWithoutDeps,
             r8LibExludeDepsPath,
             "--release",
-            repackageDeps.outputs.files
-    ).dependsOn(repackageDeps)
+            repackageDepsNew.outputs.files
+    ).dependsOn(repackageDepsNew)
+    inputs.files ([r8NoManifestWithoutDeps.outputs, repackageDepsNew.outputs])
     outputs.file r8LibExludeDepsPath
 }
 
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index e8c6bb1..3cd04b1 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -14,10 +14,10 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.conversion.IRConverter;
@@ -183,7 +183,7 @@
         ThreadUtils.processItems(
             appInfo.classes(),
             clazz -> {
-              DexEncodedMethod classInitializer = clazz.getClassInitializer();
+              ProgramMethod classInitializer = clazz.getProgramClassInitializer();
               if (classInitializer != null) {
                 analysis.processNewlyLiveMethod(classInitializer);
               }
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 164c572..5c815bc 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -375,7 +375,7 @@
     internal.debug = getMode() == CompilationMode.DEBUG;
     internal.programConsumer = getProgramConsumer();
     if (internal.programConsumer instanceof ClassFileConsumer) {
-      internal.enableCfInterfaceMethodDesugaring = true;
+      internal.cfToCfDesugar = true;
     }
     internal.mainDexListConsumer = getMainDexListConsumer();
     internal.minimalMainDex = internal.debug || minimalMainDex;
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index cf9684c..dea3bb8 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -87,11 +87,15 @@
       ExecutorService executorService)
       throws CompilationFailedException {
     try {
+      assert !options.cfToCfDesugar;
       ExceptionUtils.withD8CompilationHandler(
           options.reporter,
           () -> {
+            options.cfToCfDesugar = true;
             desugar(app, options, executorService);
+            options.cfToCfDesugar = false;
           });
+      assert !options.cfToCfDesugar;
       if (shrink) {
         R8.run(r8Command);
       } else {
@@ -105,6 +109,7 @@
   private static void desugar(
       AndroidApp inputApp, InternalOptions options, ExecutorService executor) throws IOException {
     Timing timing = Timing.create("L8 desugaring", options);
+    assert options.cfToCfDesugar;
     try {
       // Disable global optimizations.
       options.disableGlobalOptimizations();
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 0b4f28b..9a26221 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.DexAnnotation;
@@ -19,6 +21,7 @@
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
@@ -244,18 +247,18 @@
       registerTypeReference(field.field.type);
     }
 
-    private void registerMethod(DexEncodedMethod method) {
+    private void registerMethod(ProgramMethod method) {
       DexEncodedMethod superTarget =
           appInfo
-              .resolveMethod(method.holder(), method.method)
+              .resolveMethod(method.getHolder(), method.getReference())
               .lookupInvokeSpecialTarget(context, appInfo);
       if (superTarget != null) {
         addMethod(superTarget.method);
       }
-      for (DexType type : method.method.proto.parameters.values) {
+      for (DexType type : method.getDefinition().parameters().values) {
         registerTypeReference(type);
       }
-      for (DexAnnotation annotation : method.annotations().annotations) {
+      for (DexAnnotation annotation : method.getDefinition().annotations().annotations) {
         if (annotation.annotation.type == appInfo.dexItemFactory().annotationThrows) {
           DexValueArray dexValues = annotation.annotation.elements[0].value.asDexValueArray();
           for (DexValue dexValType : dexValues.getValues()) {
@@ -263,7 +266,7 @@
           }
         }
       }
-      registerTypeReference(method.method.proto.returnType);
+      registerTypeReference(method.getDefinition().returnType());
       method.registerCodeReferences(this);
     }
 
@@ -289,13 +292,11 @@
       List<DexType> directInterfaces = LambdaDescriptor.getInterfaces(callSite, appInfo);
       if (directInterfaces != null) {
         for (DexType directInterface : directInterfaces) {
-          DexClass clazz = appInfo.definitionFor(directInterface);
+          DexProgramClass clazz = asProgramClassOrNull(appInfo.definitionFor(directInterface));
           if (clazz != null) {
-            for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
-              if (encodedMethod.method.name.equals(callSite.methodName)) {
-                registerMethod(encodedMethod);
-              }
-            }
+            clazz.forEachProgramVirtualMethodMatching(
+                definition -> definition.getReference().name.equals(callSite.methodName),
+                this::registerMethod);
           }
         }
       }
@@ -360,14 +361,14 @@
 
   private void analyze() {
     UseCollector useCollector = new UseCollector(appInfo.dexItemFactory());
-    for (DexProgramClass dexProgramClass : application.classes()) {
-      useCollector.setContext(dexProgramClass);
-      useCollector.registerSuperType(dexProgramClass, dexProgramClass.superType);
-      for (DexType implementsType : dexProgramClass.interfaces.values) {
-        useCollector.registerSuperType(dexProgramClass, implementsType);
+    for (DexProgramClass clazz : application.classes()) {
+      useCollector.setContext(clazz);
+      useCollector.registerSuperType(clazz, clazz.superType);
+      for (DexType implementsType : clazz.interfaces.values) {
+        useCollector.registerSuperType(clazz, implementsType);
       }
-      dexProgramClass.forEachMethod(useCollector::registerMethod);
-      dexProgramClass.forEachField(useCollector::registerField);
+      clazz.forEachProgramMethod(useCollector::registerMethod);
+      clazz.forEachField(useCollector::registerField);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 30201ce..51b0aaf 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -32,7 +32,6 @@
 import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
-import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
@@ -52,7 +51,6 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.kotlin.KotlinInfoCollector;
-import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.Minifier;
 import com.android.tools.r8.naming.NamingLens;
@@ -682,11 +680,6 @@
                   shrinker.removeDeadBuilderReferencesFromDynamicMethods(
                       appViewWithLiveness, executorService, timing));
 
-          if (Log.ENABLED && Log.isLoggingEnabledFor(GeneratedExtensionRegistryShrinker.class)) {
-            appView.withGeneratedExtensionRegistryShrinker(
-                GeneratedExtensionRegistryShrinker::logRemainingProtoExtensionFields);
-          }
-
           if (options.isShrinking()) {
             // Mark dead proto extensions fields as neither being read nor written. This step must
             // run prior to the tree pruner.
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 59b3c8b..a2a814a 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -112,28 +112,30 @@
   }
 
   @Override
-  void writeMethod(DexEncodedMethod method, PrintStream ps) {
+  void writeMethod(ProgramMethod method, PrintStream ps) {
+    DexEncodedMethod definition = method.getDefinition();
     ClassNameMapper naming = application.getProguardMap();
-    String methodName = naming != null
-        ? naming.originalSignatureOf(method.method).name
-        : method.method.name.toString();
+    String methodName =
+        naming != null
+            ? naming.originalSignatureOf(method.getReference()).name
+            : method.getReference().name.toString();
     ps.println("#");
     ps.println("# Method: '" + methodName + "':");
-    writeAnnotations(null, method.annotations(), ps);
-    ps.println("# " + method.accessFlags);
+    writeAnnotations(null, definition.annotations(), ps);
+    ps.println("# " + definition.accessFlags);
     ps.println("#");
     ps.println();
-    Code code = method.getCode();
+    Code code = definition.getCode();
     if (code != null) {
       if (writeIR) {
         writeIR(method, ps);
       } else {
-        ps.println(code.toString(method, naming));
+        ps.println(code.toString(definition, naming));
       }
     }
   }
 
-  private void writeIR(DexEncodedMethod method, PrintStream ps) {
+  private void writeIR(ProgramMethod method, PrintStream ps) {
     CfgPrinter printer = new CfgPrinter();
     new IRConverter(appInfo, options, timing, printer)
         .processMethod(
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 93ca81d..b68b839 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -5,6 +5,8 @@
 
 import static com.android.tools.r8.graph.DexCode.FAKE_THIS_PREFIX;
 import static com.android.tools.r8.graph.DexCode.FAKE_THIS_SUFFIX;
+import static org.objectweb.asm.Opcodes.V1_5;
+import static org.objectweb.asm.Opcodes.V1_6;
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame;
@@ -211,8 +213,8 @@
     }
     for (CfInstruction instruction : instructions) {
       if (instruction instanceof CfFrame
-          && (classFileVersion <= 49
-          || (classFileVersion == 50 && !options.shouldKeepStackMapTable()))) {
+          && (classFileVersion <= V1_5
+              || (classFileVersion == V1_6 && !options.shouldKeepStackMapTable()))) {
         continue;
       }
       instruction.write(visitor, initClassLens, namingLens);
@@ -287,15 +289,14 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, AppView<?> appView, Origin origin) {
-    return internalBuildPossiblyWithLocals(
-        encodedMethod, encodedMethod, appView, null, null, origin, null);
+  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+    return internalBuildPossiblyWithLocals(method, method, appView, null, null, origin, null);
   }
 
   @Override
   public IRCode buildInliningIR(
-      DexEncodedMethod context,
-      DexEncodedMethod encodedMethod,
+      ProgramMethod context,
+      ProgramMethod method,
       AppView<?> appView,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
@@ -304,29 +305,23 @@
     assert valueNumberGenerator != null;
     assert callerPosition != null;
     return internalBuildPossiblyWithLocals(
-        context,
-        encodedMethod,
-        appView,
-        valueNumberGenerator,
-        callerPosition,
-        origin,
-        methodProcessor);
+        context, method, appView, valueNumberGenerator, callerPosition, origin, methodProcessor);
   }
 
   // First build entry. Will either strip locals or build with locals.
   private IRCode internalBuildPossiblyWithLocals(
-      DexEncodedMethod context,
-      DexEncodedMethod encodedMethod,
+      ProgramMethod context,
+      ProgramMethod method,
       AppView<?> appView,
       ValueNumberGenerator generator,
       Position callerPosition,
       Origin origin,
       MethodProcessor methodProcessor) {
-    if (!encodedMethod.keepLocals(appView.options())) {
+    if (!method.getDefinition().keepLocals(appView.options())) {
       return internalBuild(
           Collections.emptyList(),
           context,
-          encodedMethod,
+          method,
           appView,
           generator,
           callerPosition,
@@ -334,14 +329,14 @@
           methodProcessor);
     } else {
       return internalBuildWithLocals(
-          context, encodedMethod, appView, generator, callerPosition, origin, methodProcessor);
+          context, method, appView, generator, callerPosition, origin, methodProcessor);
     }
   }
 
   // When building with locals, on invalid debug info, retry build without locals info.
   private IRCode internalBuildWithLocals(
-      DexEncodedMethod context,
-      DexEncodedMethod encodedMethod,
+      ProgramMethod context,
+      ProgramMethod method,
       AppView<?> appView,
       ValueNumberGenerator generator,
       Position callerPosition,
@@ -351,18 +346,18 @@
       return internalBuild(
           Collections.unmodifiableList(localVariables),
           context,
-          encodedMethod,
+          method,
           appView,
           generator,
           callerPosition,
           origin,
           methodProcessor);
     } catch (InvalidDebugInfoException e) {
-      appView.options().warningInvalidDebugInfo(encodedMethod, origin, e);
+      appView.options().warningInvalidDebugInfo(method, origin, e);
       return internalBuild(
           Collections.emptyList(),
           context,
-          encodedMethod,
+          method,
           appView,
           generator,
           callerPosition,
@@ -374,8 +369,8 @@
   // Inner-most subroutine for building. Must only be called by the two internalBuildXYZ above.
   private IRCode internalBuild(
       List<LocalVariableInfo> localVariables,
-      DexEncodedMethod context,
-      DexEncodedMethod encodedMethod,
+      ProgramMethod context,
+      ProgramMethod method,
       AppView<?> appView,
       ValueNumberGenerator generator,
       Position callerPosition,
@@ -385,22 +380,32 @@
         new CfSourceCode(
             this,
             localVariables,
-            encodedMethod,
-            appView.graphLense().getOriginalMethodSignature(encodedMethod.method),
+            method,
+            appView.graphLense().getOriginalMethodSignature(method.getReference()),
             callerPosition,
             origin,
             appView);
-    IRBuilder builder = methodProcessor == null ?
-        IRBuilder.create(encodedMethod, appView, source, origin) :
-        IRBuilder
-            .createForInlining(encodedMethod, appView, source, origin, methodProcessor, generator);
+    IRBuilder builder =
+        methodProcessor == null
+            ? IRBuilder.create(method, appView, source, origin)
+            : IRBuilder.createForInlining(
+                method, appView, source, origin, methodProcessor, generator);
     return builder.build(context);
   }
 
   @Override
-  public void registerCodeReferences(DexEncodedMethod method, UseRegistry registry) {
+  public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
+    internalRegisterCodeReferences(method, registry);
+  }
+
+  @Override
+  public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) {
+    internalRegisterCodeReferences(method, registry);
+  }
+
+  private void internalRegisterCodeReferences(DexClassAndMethod method, UseRegistry registry) {
     for (CfInstruction instruction : instructions) {
-      instruction.registerUse(registry, method.holder());
+      instruction.registerUse(registry, method.getHolderType());
     }
     for (CfTryCatch tryCatch : tryCatchRanges) {
       for (DexType guard : tryCatch.guards) {
@@ -505,7 +510,7 @@
   }
 
   public ConstraintWithTarget computeInliningConstraint(
-      DexEncodedMethod encodedMethod,
+      ProgramMethod method,
       AppView<AppInfoWithLiveness> appView,
       GraphLense graphLense,
       DexType invocationContext) {
@@ -521,7 +526,7 @@
 
     // Model a synchronized method as having a monitor instruction.
     ConstraintWithTarget constraint =
-        encodedMethod.accessFlags.isSynchronized()
+        method.getDefinition().isSynchronized()
             ? inliningConstraints.forMonitor()
             : ConstraintWithTarget.ALWAYS;
 
diff --git a/src/main/java/com/android/tools/r8/graph/ClasspathMethod.java b/src/main/java/com/android/tools/r8/graph/ClasspathMethod.java
new file mode 100644
index 0000000..9d2a941
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ClasspathMethod.java
@@ -0,0 +1,41 @@
+// 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.graph;
+
+import com.android.tools.r8.logging.Log;
+
+/** Type representing a method definition on the classpath and its holder. */
+public final class ClasspathMethod extends DexClassAndMethod {
+
+  public ClasspathMethod(DexClasspathClass holder, DexEncodedMethod method) {
+    super(holder, method);
+  }
+
+  public void registerCodeReferencesForDesugaring(UseRegistry registry) {
+    Code code = getDefinition().getCode();
+    if (code != null) {
+      if (Log.ENABLED) {
+        Log.verbose(getClass(), "Registering definitions reachable from `%s`.", this);
+      }
+      code.registerCodeReferencesForDesugaring(this, registry);
+    }
+  }
+
+  @Override
+  public boolean isClasspathMethod() {
+    return true;
+  }
+
+  @Override
+  public ClasspathMethod asClasspathMethod() {
+    return this;
+  }
+
+  @Override
+  public DexClasspathClass getHolder() {
+    DexClass holder = super.getHolder();
+    assert holder.isClasspathClass();
+    return holder.asClasspathClass();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 8e81c8e..9f384ca 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -17,11 +17,11 @@
 
 public abstract class Code extends CachedHashValueDexItem {
 
-  public abstract IRCode buildIR(DexEncodedMethod encodedMethod, AppView<?> appView, Origin origin);
+  public abstract IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin);
 
   public IRCode buildInliningIR(
-      DexEncodedMethod context,
-      DexEncodedMethod encodedMethod,
+      ProgramMethod context,
+      ProgramMethod method,
       AppView<?> appView,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
@@ -31,7 +31,10 @@
         + getClass().getCanonicalName());
   }
 
-  public abstract void registerCodeReferences(DexEncodedMethod method, UseRegistry registry);
+  public abstract void registerCodeReferences(ProgramMethod method, UseRegistry registry);
+
+  public abstract void registerCodeReferencesForDesugaring(
+      ClasspathMethod method, UseRegistry registry);
 
   public void registerArgumentReferences(DexEncodedMethod method, ArgumentUse registry) {
     throw new Unreachable();
diff --git a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
index a81e59c..c83010d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
@@ -88,7 +88,7 @@
     clazz.forEachField(field -> writeField(field, ps));
     writeFieldsFooter(clazz, ps);
     writeMethodsHeader(clazz, ps);
-    clazz.forEachMethod(method -> writeMethod(method, ps));
+    clazz.forEachProgramMethod(method -> writeMethod(method, ps));
     writeMethodsFooter(clazz, ps);
     writeClassFooter(clazz, ps);
   }
@@ -111,7 +111,7 @@
     // Do nothing.
   }
 
-  abstract void writeMethod(DexEncodedMethod method, PrintStream ps);
+  abstract void writeMethod(ProgramMethod method, PrintStream ps);
 
   void writeMethodsFooter(DexProgramClass clazz, PrintStream ps) {
     // Do nothing.
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 444459f..5bcb25c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -511,6 +511,10 @@
     return null;
   }
 
+  public boolean isPrivate() {
+    return accessFlags.isPrivate();
+  }
+
   public boolean isPublic() {
     return accessFlags.isPublic();
   }
@@ -822,7 +826,7 @@
 
   public boolean hasStaticSynchronizedMethods() {
     for (DexEncodedMethod encodedMethod : directMethods()) {
-      if (encodedMethod.accessFlags.isStatic() && encodedMethod.accessFlags.isSynchronized()) {
+      if (encodedMethod.isStatic() && encodedMethod.isSynchronized()) {
         return true;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index f77d55b..c6c2f07 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
 
 public class DexClassAndMethod implements LookupTarget {
 
@@ -12,7 +13,10 @@
   private final DexEncodedMethod method;
 
   DexClassAndMethod(DexClass holder, DexEncodedMethod method) {
+    assert holder != null;
+    assert method != null;
     assert holder.type == method.holder();
+    assert holder.isProgramClass() == (this instanceof ProgramMethod);
     this.holder = holder;
     this.method = method;
   }
@@ -20,13 +24,16 @@
   public static DexClassAndMethod create(DexClass holder, DexEncodedMethod method) {
     if (holder.isProgramClass()) {
       return new ProgramMethod(holder.asProgramClass(), method);
-    } else {
+    } else if (holder.isLibraryClass()) {
       return new DexClassAndMethod(holder, method);
+    } else {
+      assert holder.isClasspathClass();
+      return new ClasspathMethod(holder.asClasspathClass(), method);
     }
   }
 
   @Override
-  public boolean equals(Object obj) {
+  public boolean equals(Object object) {
     throw new Unreachable("Unsupported attempt at comparing Class and DexClassAndMethod");
   }
 
@@ -49,10 +56,30 @@
     return holder;
   }
 
+  public DexType getHolderType() {
+    return holder.type;
+  }
+
   public DexEncodedMethod getDefinition() {
     return method;
   }
 
+  public DexMethod getReference() {
+    return method.method;
+  }
+
+  public Origin getOrigin() {
+    return holder.origin;
+  }
+
+  public boolean isClasspathMethod() {
+    return false;
+  }
+
+  public ClasspathMethod asClasspathMethod() {
+    return null;
+  }
+
   public boolean isProgramMethod() {
     return false;
   }
@@ -60,4 +87,13 @@
   public ProgramMethod asProgramMethod() {
     return null;
   }
+
+  public String toSourceString() {
+    return method.method.toSourceString();
+  }
+
+  @Override
+  public String toString() {
+    return toSourceString();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index e9e47b6..b4eeb9e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -214,20 +214,20 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, AppView<?> appView, Origin origin) {
+  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
     DexSourceCode source =
         new DexSourceCode(
             this,
-            encodedMethod,
-            appView.graphLense().getOriginalMethodSignature(encodedMethod.method),
+            method,
+            appView.graphLense().getOriginalMethodSignature(method.getReference()),
             null);
-    return IRBuilder.create(encodedMethod,appView,source,origin).build(encodedMethod);
+    return IRBuilder.create(method, appView, source, origin).build(method);
   }
 
   @Override
   public IRCode buildInliningIR(
-      DexEncodedMethod context,
-      DexEncodedMethod encodedMethod,
+      ProgramMethod context,
+      ProgramMethod method,
       AppView<?> appView,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
@@ -236,15 +236,25 @@
     DexSourceCode source =
         new DexSourceCode(
             this,
-            encodedMethod,
-            appView.graphLense().getOriginalMethodSignature(encodedMethod.method),
+            method,
+            appView.graphLense().getOriginalMethodSignature(method.getReference()),
             callerPosition);
     return IRBuilder.createForInlining(
-        encodedMethod, appView, source, origin, methodProcessor, valueNumberGenerator).build(context);
+            method, appView, source, origin, methodProcessor, valueNumberGenerator)
+        .build(context);
   }
 
   @Override
-  public void registerCodeReferences(DexEncodedMethod method, UseRegistry registry) {
+  public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
+    internalRegisterCodeReferences(method, registry);
+  }
+
+  @Override
+  public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) {
+    internalRegisterCodeReferences(method, registry);
+  }
+
+  private void internalRegisterCodeReferences(DexClassAndMethod method, UseRegistry registry) {
     for (Instruction insn : instructions) {
       insn.registerUse(registry);
     }
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 43536b9..c589524 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -207,7 +207,7 @@
           && singleValue.asSingleFieldValue().getField() == field) {
         return null;
       }
-      if (singleValue.isMaterializableInContext(appView, code.method().holder())) {
+      if (singleValue.isMaterializableInContext(appView, code.context())) {
         TypeElement type = TypeElement.fromDexType(field.type, maybeNull(), appView);
         return singleValue.createMaterializingInstruction(
             appView, code, TypeAndLocalInfoSupplier.create(type, local));
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 816a515..b81c877 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -9,6 +9,7 @@
 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_SAME_PACKAGE;
 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_SUBCLASS;
 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
 
 import com.android.tools.r8.cf.code.CfConstNull;
@@ -37,11 +38,8 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring.DexFieldWithAccess;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -57,12 +55,10 @@
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.kotlin.KotlinMethodLevelInfo;
-import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AnnotationRemover;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -257,10 +253,18 @@
     assert parameterAnnotationsList != null;
   }
 
+  public DexMethod getReference() {
+    return method;
+  }
+
   public DexTypeList parameters() {
     return method.proto.parameters;
   }
 
+  public DexProto proto() {
+    return method.proto;
+  }
+
   public DexType returnType() {
     return method.proto.returnType;
   }
@@ -298,6 +302,20 @@
     return false;
   }
 
+  public ProgramMethod asProgramMethod(DexDefinitionSupplier definitions) {
+    assert method.holder.isClassType();
+    DexProgramClass clazz = asProgramClassOrNull(definitions.definitionForHolder(method));
+    if (clazz != null) {
+      return new ProgramMethod(clazz, this);
+    }
+    return null;
+  }
+
+  public static ProgramMethod asProgramMethodOrNull(
+      DexEncodedMethod method, DexDefinitionSupplier definitions) {
+    return method != null ? method.asProgramMethod(definitions) : null;
+  }
+
   public boolean isProcessed() {
     checkIfObsolete();
     return compilationState != CompilationState.NOT_PROCESSED;
@@ -315,10 +333,22 @@
     return accessFlags.isFinal();
   }
 
+  public boolean isNative() {
+    return accessFlags.isNative();
+  }
+
+  public boolean isPrivate() {
+    return accessFlags.isPrivate();
+  }
+
   public boolean isPublic() {
     return accessFlags.isPublic();
   }
 
+  public boolean isSynchronized() {
+    return accessFlags.isSynchronized();
+  }
+
   public boolean isInitializer() {
     checkIfObsolete();
     return isInstanceInitializer() || isClassInitializer();
@@ -448,13 +478,13 @@
   }
 
   public boolean isInliningCandidate(
-      DexEncodedMethod container,
+      ProgramMethod container,
       Reason inliningReason,
       AppInfoWithClassHierarchy appInfo,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     checkIfObsolete();
     return isInliningCandidate(
-        container.holder(), inliningReason, appInfo, whyAreYouNotInliningReporter);
+        container.getHolderType(), inliningReason, appInfo, whyAreYouNotInliningReporter);
   }
 
   public boolean isInliningCandidate(
@@ -552,23 +582,6 @@
     compilationState = CompilationState.NOT_PROCESSED;
   }
 
-  public IRCode buildIR(AppView<?> appView, Origin origin) {
-    checkIfObsolete();
-    return code == null ? null : code.buildIR(this, appView, origin);
-  }
-
-  public IRCode buildInliningIR(
-      DexEncodedMethod context,
-      AppView<?> appView,
-      ValueNumberGenerator valueNumberGenerator,
-      Position callerPosition,
-      Origin origin,
-      MethodProcessor methodProcessor) {
-    checkIfObsolete();
-    return code.buildInliningIR(
-        context, this, appView, valueNumberGenerator, callerPosition, origin, methodProcessor);
-  }
-
   public void setCode(Code newCode, AppView<?> appView) {
     checkIfObsolete();
     // If the locals are not kept, we might still need information to satisfy -keepparameternames.
@@ -936,7 +949,7 @@
     return builder.build();
   }
 
-  public DexEncodedMethod toInitializerForwardingBridge(DexClass holder, DexMethod newMethod) {
+  public ProgramMethod toInitializerForwardingBridge(DexProgramClass holder, DexMethod newMethod) {
     assert accessFlags.isPrivate()
         : "Expected to create bridge for private constructor as part of nest-based access"
             + " desugaring";
@@ -958,11 +971,11 @@
     builder.accessFlags.unsetPrivate();
     builder.accessFlags.setSynthetic();
     builder.accessFlags.setConstructor();
-    return builder.build();
+    return new ProgramMethod(holder, builder.build());
   }
 
-  public static DexEncodedMethod createFieldAccessorBridge(
-      DexFieldWithAccess fieldWithAccess, DexClass holder, DexMethod newMethod) {
+  public static ProgramMethod createFieldAccessorBridge(
+      DexFieldWithAccess fieldWithAccess, DexProgramClass holder, DexMethod newMethod) {
     assert holder.type == fieldWithAccess.getHolder();
     MethodAccessFlags accessFlags =
         MethodAccessFlags.fromSharedAccessFlags(
@@ -987,13 +1000,15 @@
                 registry.registerStaticFieldWrite(fieldWithAccess.getField());
               }
             });
-    return new DexEncodedMethod(
-        newMethod,
-        accessFlags,
-        DexAnnotationSet.empty(),
-        ParameterAnnotationsList.empty(),
-        code,
-        true);
+    return new ProgramMethod(
+        holder,
+        new DexEncodedMethod(
+            newMethod,
+            accessFlags,
+            DexAnnotationSet.empty(),
+            ParameterAnnotationsList.empty(),
+            code,
+            true));
   }
 
   public DexEncodedMethod toRenamedHolderMethod(DexType newHolderType, DexItemFactory factory) {
@@ -1025,7 +1040,7 @@
         true);
   }
 
-  public DexEncodedMethod toStaticForwardingBridge(DexClass holder, DexMethod newMethod) {
+  public ProgramMethod toStaticForwardingBridge(DexProgramClass holder, DexMethod newMethod) {
     assert accessFlags.isPrivate()
         : "Expected to create bridge for private method as part of nest-based access desugaring";
     Builder builder = syntheticBuilder(this);
@@ -1053,7 +1068,7 @@
     if (holder.isInterface()) {
       builder.accessFlags.setPublic();
     }
-    return builder.build();
+    return new ProgramMethod(holder, builder.build());
   }
 
   public DexEncodedMethod toForwardingMethod(DexClass holder, DexDefinitionSupplier definitions) {
@@ -1198,16 +1213,6 @@
     return !annotations().isEmpty() || !parameterAnnotationsList.isEmpty();
   }
 
-  public void registerCodeReferences(UseRegistry registry) {
-    checkIfObsolete();
-    if (code != null) {
-      if (Log.ENABLED) {
-        Log.verbose(getClass(), "Registering definitions reachable from `%s`.", method);
-      }
-      code.registerCodeReferences(this, registry);
-    }
-  }
-
   public static int slowCompare(DexEncodedMethod m1, DexEncodedMethod m2) {
     return m1.method.slowCompareTo(m2.method);
   }
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 bda291b..e27c7b4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
+import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.ProgramResource;
 import com.android.tools.r8.ProgramResource.Kind;
@@ -14,6 +15,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.TraversalContinuation;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -21,6 +23,9 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
 import java.util.function.Supplier;
 
 public class DexProgramClass extends DexClass implements Supplier<DexProgramClass> {
@@ -128,6 +133,79 @@
     synthesizedDirectlyFrom.forEach(this::addSynthesizedFrom);
   }
 
+  public void forEachProgramMethod(Consumer<ProgramMethod> consumer) {
+    forEachProgramMethodMatching(alwaysTrue(), consumer);
+  }
+
+  public void forEachProgramMethodMatching(
+      Predicate<DexEncodedMethod> predicate, Consumer<ProgramMethod> consumer) {
+    methodCollection.forEachMethodMatching(
+        predicate, method -> consumer.accept(new ProgramMethod(this, method)));
+  }
+
+  public void forEachProgramDirectMethod(Consumer<ProgramMethod> consumer) {
+    forEachProgramDirectMethodMatching(alwaysTrue(), consumer);
+  }
+
+  public void forEachProgramDirectMethodMatching(
+      Predicate<DexEncodedMethod> predicate, Consumer<ProgramMethod> consumer) {
+    methodCollection.forEachDirectMethodMatching(
+        predicate, method -> consumer.accept(new ProgramMethod(this, method)));
+  }
+
+  public void forEachProgramVirtualMethod(Consumer<ProgramMethod> consumer) {
+    forEachProgramVirtualMethodMatching(alwaysTrue(), consumer);
+  }
+
+  public void forEachProgramVirtualMethodMatching(
+      Predicate<DexEncodedMethod> predicate, Consumer<ProgramMethod> consumer) {
+    methodCollection.forEachVirtualMethodMatching(
+        predicate, method -> consumer.accept(new ProgramMethod(this, method)));
+  }
+
+  public ProgramMethod getProgramClassInitializer() {
+    return toProgramMethodOrNull(getClassInitializer());
+  }
+
+  public ProgramMethod getProgramDefaultInitializer() {
+    return getProgramInitializer(DexType.EMPTY_ARRAY);
+  }
+
+  public ProgramMethod getProgramInitializer(DexType[] types) {
+    return toProgramMethodOrNull(getInitializer(types));
+  }
+
+  public ProgramMethod lookupProgramMethod(DexMethod reference) {
+    return toProgramMethodOrNull(getMethodCollection().getMethod(reference));
+  }
+
+  private ProgramMethod toProgramMethodOrNull(DexEncodedMethod method) {
+    if (method != null) {
+      return new ProgramMethod(this, method);
+    }
+    return null;
+  }
+
+  public TraversalContinuation traverseProgramMethods(
+      Function<ProgramMethod, TraversalContinuation> fn) {
+    return getMethodCollection().traverse(method -> fn.apply(new ProgramMethod(this, method)));
+  }
+
+  public TraversalContinuation traverseProgramInstanceInitializers(
+      Function<ProgramMethod, TraversalContinuation> fn) {
+    return traverseProgramMethods(fn, DexEncodedMethod::isInstanceInitializer);
+  }
+
+  public TraversalContinuation traverseProgramMethods(
+      Function<ProgramMethod, TraversalContinuation> fn, Predicate<DexEncodedMethod> predicate) {
+    return getMethodCollection()
+        .traverse(
+            method ->
+                predicate.test(method)
+                    ? fn.apply(new ProgramMethod(this, method))
+                    : TraversalContinuation.CONTINUE);
+  }
+
   public boolean originatesFromDexResource() {
     return originKind == Kind.DEX;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 597e6e0..d17a4d2 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -209,14 +209,14 @@
   }
 
   @Override
-  public IRCode buildIR(DexEncodedMethod encodedMethod, AppView<?> appView, Origin origin) {
-    return asCfCode().buildIR(encodedMethod, appView, origin);
+  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+    return asCfCode().buildIR(method, appView, origin);
   }
 
   @Override
   public IRCode buildInliningIR(
-      DexEncodedMethod context,
-      DexEncodedMethod encodedMethod,
+      ProgramMethod context,
+      ProgramMethod method,
       AppView<?> appView,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
@@ -225,7 +225,7 @@
     return asCfCode()
         .buildInliningIR(
             context,
-            encodedMethod,
+            method,
             appView,
             valueNumberGenerator,
             callerPosition,
@@ -234,11 +234,16 @@
   }
 
   @Override
-  public void registerCodeReferences(DexEncodedMethod method, UseRegistry registry) {
+  public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
     asCfCode().registerCodeReferences(method, registry);
   }
 
   @Override
+  public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) {
+    asCfCode().registerCodeReferencesForDesugaring(method, registry);
+  }
+
+  @Override
   public void registerArgumentReferences(DexEncodedMethod method, ArgumentUse registry) {
     asCfCode().registerArgumentReferences(method, registry);
   }
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 5b05c3f..5a5db62 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
@@ -38,6 +38,16 @@
   }
 
   @Override
+  public int numberOfDirectMethods() {
+    return directMethods.length;
+  }
+
+  @Override
+  public int numberOfVirtualMethods() {
+    return virtualMethods.length;
+  }
+
+  @Override
   int size() {
     return directMethods.length + virtualMethods.length;
   }
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 ec3e102..ac2685a 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -1,5 +1,7 @@
 package com.android.tools.r8.graph;
 
+import static com.google.common.base.Predicates.alwaysTrue;
+
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.TraversalContinuation;
@@ -48,6 +50,14 @@
     // Nothing to do.
   }
 
+  public int numberOfDirectMethods() {
+    return backing.numberOfDirectMethods();
+  }
+
+  public int numberOfVirtualMethods() {
+    return backing.numberOfVirtualMethods();
+  }
+
   public int size() {
     return backing.size();
   }
@@ -57,7 +67,45 @@
   }
 
   public void forEachMethod(Consumer<DexEncodedMethod> consumer) {
-    backing.forEachMethod(consumer);
+    forEachMethodMatching(alwaysTrue(), consumer);
+  }
+
+  public void forEachMethodMatching(
+      Predicate<DexEncodedMethod> predicate, Consumer<DexEncodedMethod> consumer) {
+    backing.forEachMethod(
+        method -> {
+          if (predicate.test(method)) {
+            consumer.accept(method);
+          }
+        });
+  }
+
+  public void forEachDirectMethod(Consumer<DexEncodedMethod> consumer) {
+    forEachDirectMethodMatching(alwaysTrue(), consumer);
+  }
+
+  public void forEachDirectMethodMatching(
+      Predicate<DexEncodedMethod> predicate, Consumer<DexEncodedMethod> consumer) {
+    backing.forEachDirectMethod(
+        method -> {
+          if (predicate.test(method)) {
+            consumer.accept(method);
+          }
+        });
+  }
+
+  public void forEachVirtualMethod(Consumer<DexEncodedMethod> consumer) {
+    forEachVirtualMethodMatching(alwaysTrue(), consumer);
+  }
+
+  public void forEachVirtualMethodMatching(
+      Predicate<DexEncodedMethod> predicate, Consumer<DexEncodedMethod> consumer) {
+    backing.forEachVirtualMethod(
+        method -> {
+          if (predicate.test(method)) {
+            consumer.accept(method);
+          }
+        });
   }
 
   public Iterable<DexEncodedMethod> methods() {
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
index 913a563..56ab228 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static com.google.common.base.Predicates.alwaysTrue;
+
 import com.android.tools.r8.utils.TraversalContinuation;
 import java.util.Collection;
 import java.util.List;
@@ -29,6 +31,10 @@
 
   // Collection methods.
 
+  abstract int numberOfDirectMethods();
+
+  abstract int numberOfVirtualMethods();
+
   abstract int size();
 
   // Traversal methods.
@@ -36,13 +42,27 @@
   abstract TraversalContinuation traverse(Function<DexEncodedMethod, TraversalContinuation> fn);
 
   void forEachMethod(Consumer<DexEncodedMethod> fn) {
+    forEachMethod(fn, alwaysTrue());
+  }
+
+  void forEachMethod(Consumer<DexEncodedMethod> fn, Predicate<DexEncodedMethod> predicate) {
     traverse(
         method -> {
-          fn.accept(method);
+          if (predicate.test(method)) {
+            fn.accept(method);
+          }
           return TraversalContinuation.CONTINUE;
         });
   }
 
+  void forEachDirectMethod(Consumer<DexEncodedMethod> fn) {
+    forEachMethod(fn, this::belongsToDirectPool);
+  }
+
+  void forEachVirtualMethod(Consumer<DexEncodedMethod> fn) {
+    forEachMethod(fn, this::belongsToVirtualPool);
+  }
+
   abstract Iterable<DexEncodedMethod> methods();
 
   abstract List<DexEncodedMethod> directMethods();
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 d03e741..d34ba36 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
@@ -66,6 +66,26 @@
   }
 
   @Override
+  public int numberOfDirectMethods() {
+    return numberOfMethodsMatching(this::belongsToDirectPool);
+  }
+
+  @Override
+  public int numberOfVirtualMethods() {
+    return numberOfMethodsMatching(this::belongsToVirtualPool);
+  }
+
+  private int numberOfMethodsMatching(Predicate<DexEncodedMethod> predicate) {
+    int count = 0;
+    for (DexEncodedMethod method : methodMap.values()) {
+      if (predicate.test(method)) {
+        count++;
+      }
+    }
+    return count;
+  }
+
+  @Override
   int size() {
     return methodMap.size();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index 34f32ff..1245da9 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -271,7 +271,7 @@
      */
     public boolean recordDirectAllocationSite(
         DexProgramClass clazz,
-        DexEncodedMethod context,
+        ProgramMethod context,
         InstantiationReason instantiationReason,
         KeepReason keepReason,
         AppInfo appInfo) {
@@ -285,7 +285,7 @@
         Set<DexEncodedMethod> allocationSitesForClass =
             classesWithAllocationSiteTracking.computeIfAbsent(
                 clazz, ignore -> Sets.newIdentityHashSet());
-        allocationSitesForClass.add(context);
+        allocationSitesForClass.add(context.getDefinition());
         return allocationSitesForClass.size() == 1;
       }
       if (classesWithoutAllocationSiteTracking.add(clazz)) {
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 4924d0c..88c781c 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -3,6 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.origin.Origin;
+
 /** Type representing a method definition in the programs compilation unit and its holder. */
 public final class ProgramMethod extends DexClassAndMethod {
 
@@ -10,6 +17,37 @@
     super(holder, method);
   }
 
+  public IRCode buildIR(AppView<?> appView) {
+    DexEncodedMethod method = getDefinition();
+    return method.hasCode() ? method.getCode().buildIR(this, appView, getOrigin()) : null;
+  }
+
+  public IRCode buildInliningIR(
+      ProgramMethod context,
+      AppView<?> appView,
+      ValueNumberGenerator valueNumberGenerator,
+      Position callerPosition,
+      Origin origin,
+      MethodProcessor methodProcessor) {
+    Code code = getDefinition().getCode();
+    return code.buildInliningIR(
+        context, this, appView, valueNumberGenerator, callerPosition, origin, methodProcessor);
+  }
+
+  public boolean isStructurallyEqualTo(ProgramMethod other) {
+    return getDefinition() == other.getDefinition() && getHolder() == other.getHolder();
+  }
+
+  public void registerCodeReferences(UseRegistry registry) {
+    Code code = getDefinition().getCode();
+    if (code != null) {
+      if (Log.ENABLED) {
+        Log.verbose(getClass(), "Registering definitions reachable from `%s`.", this);
+      }
+      code.registerCodeReferences(this, registry);
+    }
+  }
+
   @Override
   public boolean isProgramMethod() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
index c632f75..0f63ed1 100644
--- a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
@@ -68,9 +68,9 @@
   }
 
   @Override
-  void writeMethod(DexEncodedMethod method, PrintStream ps) {
+  void writeMethod(ProgramMethod method, PrintStream ps) {
     ps.append("\n");
-    ps.append(method.toSmaliString(application.getProguardMap()));
+    ps.append(method.getDefinition().toSmaliString(application.getProguardMap()));
     ps.append("\n");
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
index b41e8fa..6b03d33 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
@@ -12,9 +12,11 @@
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLogicalBinop;
 import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -33,12 +35,13 @@
   }
 
   @Override
-  public void processNewlyLiveMethod(DexEncodedMethod method) {
-    if (method.isClassInitializer()) {
-      if (method.getCode().isCfCode()) {
-        if (hasJavacClinitAssertionCode(method.getCode().asCfCode())
-            || hasKotlincClinitAssertionCode(method)) {
-          feedback.setInitializerEnablingJavaVmAssertions(method);
+  public void processNewlyLiveMethod(ProgramMethod method) {
+    DexEncodedMethod definition = method.getDefinition();
+    if (definition.isClassInitializer()) {
+      Code code = definition.getCode();
+      if (code.isCfCode()) {
+        if (hasJavacClinitAssertionCode(code.asCfCode()) || hasKotlincClinitAssertionCode(method)) {
+          feedback.setInitializerEnablingJavaVmAssertions(definition);
         }
       }
     }
@@ -126,9 +129,9 @@
     return false;
   }
 
-  private boolean hasKotlincClinitAssertionCode(DexEncodedMethod method) {
-    if (method.holder() == dexItemFactory.kotlin.assertions.type) {
-      CfCode code = method.getCode().asCfCode();
+  private boolean hasKotlincClinitAssertionCode(ProgramMethod method) {
+    if (method.getHolderType() == dexItemFactory.kotlin.assertions.type) {
+      CfCode code = method.getDefinition().getCode().asCfCode();
       for (int i = 1; i < code.instructions.size(); i++) {
         CfInstruction instruction = code.instructions.get(i - 1);
         if (instruction.isInvoke()) {
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
index 8d61294..96b5de8 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.Mode;
 import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -33,7 +34,7 @@
   }
 
   @Override
-  public void processNewlyLiveMethod(DexEncodedMethod method) {
+  public void processNewlyLiveMethod(ProgramMethod method) {
     converter.registerCallbackIfRequired(method);
   }
 
@@ -66,7 +67,7 @@
     this.traceInvoke(invokedMethod);
   }
 
-  public List<DexEncodedMethod> generateCallbackMethods() {
+  public ProgramMethodSet generateCallbackMethods() {
     return converter.generateCallbackMethods();
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
index 29d7dcc..06f544d 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
@@ -5,8 +5,8 @@
 package com.android.tools.r8.graph.analysis;
 
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
 import com.android.tools.r8.utils.Timing;
@@ -14,7 +14,7 @@
 public abstract class EnqueuerAnalysis {
 
   /** Called when a class is found to be instantiated. */
-  public void processNewlyInstantiatedClass(DexProgramClass clazz, DexEncodedMethod context) {}
+  public void processNewlyInstantiatedClass(DexProgramClass clazz, ProgramMethod context) {}
 
   /** Called when a class is found to be live. */
   public void processNewlyLiveClass(DexProgramClass clazz, EnqueuerWorklist worklist) {}
@@ -23,7 +23,7 @@
   public void processNewlyLiveField(DexEncodedField field) {}
 
   /** Called when a method is found to be live. */
-  public void processNewlyLiveMethod(DexEncodedMethod method) {}
+  public void processNewlyLiveMethod(ProgramMethod method) {}
 
   /**
    * Called when the Enqueuer reaches a fixpoint. This may happen multiple times, since each
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
index 04dbef2..48518f5 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
@@ -7,9 +7,9 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -62,7 +62,7 @@
   }
 
   @Override
-  public void processNewlyInstantiatedClass(DexProgramClass clazz, DexEncodedMethod context) {
+  public void processNewlyInstantiatedClass(DexProgramClass clazz, ProgramMethod context) {
     DexType key = clazz.type;
     DexType objectType = appView.dexItemFactory().objectType;
     if (context == null) {
@@ -74,7 +74,7 @@
 
     // Record that the enclosing class is guaranteed to be initialized at the allocation site.
     AppInfoWithClassHierarchy appInfo = appView.appInfo();
-    DexType guaranteedToBeInitialized = context.holder();
+    DexType guaranteedToBeInitialized = context.getHolderType();
     DexType existingGuaranteedToBeInitialized =
         mapping.getOrDefault(key, guaranteedToBeInitialized);
     mapping.put(
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 75d5423..5e29668 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
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.BottomValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
@@ -28,11 +29,11 @@
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -275,11 +276,11 @@
     }
   }
 
-  public void waveDone(Collection<DexEncodedMethod> wave, OptimizationFeedbackDelayed feedback) {
+  public void waveDone(ProgramMethodSet wave, OptimizationFeedbackDelayed feedback) {
     // This relies on the instance initializer info in the method optimization feedback. It is
     // therefore important that the optimization info has been flushed in advance.
     assert feedback.noUpdatesLeft();
-    for (DexEncodedMethod method : wave) {
+    for (ProgramMethod method : wave) {
       fieldAccessGraph.markProcessed(method, field -> recordAllFieldPutsProcessed(field, feedback));
       objectAllocationGraph.markProcessed(
           method, clazz -> recordAllAllocationsSitesProcessed(clazz, feedback));
@@ -328,8 +329,8 @@
           });
     }
 
-    void markProcessed(DexEncodedMethod method, Consumer<DexEncodedField> allWritesSeenConsumer) {
-      List<DexEncodedField> fieldWritesInMethod = fieldWrites.get(method);
+    void markProcessed(ProgramMethod method, Consumer<DexEncodedField> allWritesSeenConsumer) {
+      List<DexEncodedField> fieldWritesInMethod = fieldWrites.get(method.getDefinition());
       if (fieldWritesInMethod != null) {
         for (DexEncodedField field : fieldWritesInMethod) {
           int numberOfPendingFieldWrites = pendingFieldWrites.removeInt(field) - 1;
@@ -366,8 +367,8 @@
     }
 
     void markProcessed(
-        DexEncodedMethod method, Consumer<DexProgramClass> allAllocationsSitesSeenConsumer) {
-      List<DexProgramClass> allocationSitesInMethod = objectAllocations.get(method);
+        ProgramMethod method, Consumer<DexProgramClass> allAllocationsSitesSeenConsumer) {
+      List<DexProgramClass> allocationSitesInMethod = objectAllocations.get(method.getDefinition());
       if (allocationSitesInMethod != null) {
         for (DexProgramClass type : allocationSitesInMethod) {
           int numberOfPendingAllocationSites = pendingObjectAllocations.removeInt(type) - 1;
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 5ddacd6..1a6a707 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
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
@@ -24,6 +25,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.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -38,7 +40,7 @@
   private final Set<DexEncodedField> fieldsOfInterest = Sets.newConcurrentHashSet();
 
   /** Updated concurrently from {@link #processClass(DexProgramClass)}. */
-  private final Set<DexEncodedMethod> methodsToReprocess = Sets.newConcurrentHashSet();
+  private final ProgramMethodSet methodsToReprocess = ProgramMethodSet.createConcurrent();
 
   public TrivialFieldAccessReprocessor(
       AppView<AppInfoWithLiveness> appView,
@@ -109,11 +111,9 @@
   }
 
   private void processClass(DexProgramClass clazz) {
-    for (DexEncodedMethod method : clazz.methods()) {
-      if (method.hasCode()) {
-        method.getCode().registerCodeReferences(method, new TrivialFieldAccessUseRegistry(method));
-      }
-    }
+    clazz.forEachProgramMethodMatching(
+        DexEncodedMethod::hasCode,
+        method -> method.registerCodeReferences(new TrivialFieldAccessUseRegistry(method)));
   }
 
   private static boolean canOptimizeField(
@@ -154,9 +154,9 @@
 
   class TrivialFieldAccessUseRegistry extends UseRegistry {
 
-    private final DexEncodedMethod method;
+    private final ProgramMethod method;
 
-    TrivialFieldAccessUseRegistry(DexEncodedMethod method) {
+    TrivialFieldAccessUseRegistry(ProgramMethod method) {
       super(appView.dexItemFactory());
       this.method = method;
     }
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 3e493b7..4879783 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
@@ -4,19 +4,17 @@
 
 package com.android.tools.r8.ir.analysis.proto;
 
-import static com.google.common.base.Predicates.not;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DefaultUseRegistry;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.IRCodeUtils;
 import com.android.tools.r8.ir.code.Instruction;
@@ -24,27 +22,19 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
-import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.DefaultTreePrunerConfiguration;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.TreePrunerConfiguration;
-import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.base.Predicates;
 import com.google.common.collect.Sets;
-import java.io.IOException;
-import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
 
 /**
  * This optimization is responsible for pruning dead proto extensions.
@@ -173,18 +163,16 @@
     timing.end();
   }
 
-  private void forEachFindLiteExtensionByNumberMethod(Consumer<DexEncodedMethod> consumer) {
+  private void forEachFindLiteExtensionByNumberMethod(Consumer<ProgramMethod> consumer) {
     appView
         .appInfo()
         .forEachInstantiatedSubType(
             references.extensionRegistryLiteType,
-            clazz -> {
-              for (DexEncodedMethod method : clazz.methods()) {
-                if (references.isFindLiteExtensionByNumberMethod(method.method)) {
-                  consumer.accept(method);
-                }
-              }
-            },
+            clazz ->
+                clazz.forEachProgramMethodMatching(
+                    definition ->
+                        references.isFindLiteExtensionByNumberMethod(definition.getReference()),
+                    consumer::accept),
             lambda -> {
               assert false;
             });
@@ -239,93 +227,4 @@
           }
         });
   }
-
-  /** For debugging. */
-  public void logRemainingProtoExtensionFields() {
-    Predicate<DexField> skip = getSkipPredicate(null);
-
-    Set<DexField> remainingProtoExtensionFieldReads = Sets.newIdentityHashSet();
-    forEachFindLiteExtensionByNumberMethod(
-        method -> {
-          Log.info(
-              GeneratedExtensionRegistryShrinker.class,
-              "Extracting remaining proto extension field reads from method `%s`",
-              method.method.toSourceString());
-
-          assert method.hasCode();
-          method
-              .getCode()
-              .registerCodeReferences(
-                  method,
-                  new DefaultUseRegistry(appView.dexItemFactory()) {
-
-                    @Override
-                    public boolean registerStaticFieldRead(DexField field) {
-                      if (!skip.test(field)) {
-                        remainingProtoExtensionFieldReads.add(field);
-                      }
-                      return true;
-                    }
-                  });
-        });
-
-    Log.info(
-        GeneratedExtensionRegistryShrinker.class,
-        "Number of remaining proto extension fields: %s",
-        remainingProtoExtensionFieldReads.size());
-
-    FieldAccessInfoCollection<?> fieldAccessInfoCollection =
-        appView.appInfo().getFieldAccessInfoCollection();
-    for (DexField field : remainingProtoExtensionFieldReads) {
-      StringBuilder message = new StringBuilder(field.toSourceString());
-      FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field);
-      fieldAccessInfo.forEachReadContext(
-          readContext ->
-              message
-                  .append(System.lineSeparator())
-                  .append("- ")
-                  .append(readContext.toSourceString()));
-      Log.info(GeneratedExtensionRegistryShrinker.class, message.toString());
-    }
-  }
-
-  /**
-   * Utility to disable logging for proto extensions fields that are expected to be present in the
-   * output.
-   *
-   * <p>Each proto extension field that is expected to be present in the output can be added to the
-   * given file. Then no logs will be emitted for that field.
-   *
-   * <p>Example: File expected-proto-extensions.txt with lines like this:
-   *
-   * <pre>
-   *   foo.bar.SomeClass.someField
-   *   foo.bar.SomeOtherClass.someOtherField
-   * </pre>
-   */
-  private Predicate<DexField> getSkipPredicate(Path file) {
-    if (file != null) {
-      try {
-        DexItemFactory dexItemFactory = appView.dexItemFactory();
-        Set<DexField> skipFields =
-            FileUtils.readAllLines(file).stream()
-                .map(String::trim)
-                .filter(not(String::isEmpty))
-                .map(
-                    x -> {
-                      int separatorIndex = x.lastIndexOf(".");
-                      return dexItemFactory.createField(
-                          dexItemFactory.createType(
-                              DescriptorUtils.javaTypeToDescriptor(x.substring(0, separatorIndex))),
-                          references.generatedExtensionType,
-                          dexItemFactory.createString(x.substring(separatorIndex + 1)));
-                    })
-                .collect(Collectors.toSet());
-        return skipFields::contains;
-      } catch (IOException e) {
-        throw new RuntimeException(e);
-      }
-    }
-    return Predicates.alwaysFalse();
-  }
 }
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 f073108..a11b346 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
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -34,7 +35,6 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.ir.optimize.inliner.FixedInliningReasonStrategy;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.PredicateSet;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -54,7 +54,7 @@
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final ProtoReferences references;
 
-  private final Map<DexProgramClass, DexEncodedMethod> builders = new IdentityHashMap<>();
+  private final Map<DexProgramClass, ProgramMethod> builders = new IdentityHashMap<>();
 
   GeneratedMessageLiteBuilderShrinker(
       AppView<? extends AppInfoWithClassHierarchy> appView, ProtoReferences references) {
@@ -64,11 +64,12 @@
 
   /** Returns true if an action was deferred. */
   public boolean deferDeadProtoBuilders(
-      DexProgramClass clazz, DexEncodedMethod context, BooleanSupplier register) {
-    if (references.isDynamicMethod(context) && references.isGeneratedMessageLiteBuilder(clazz)) {
+      DexProgramClass clazz, ProgramMethod method, BooleanSupplier register) {
+    DexEncodedMethod definition = method.getDefinition();
+    if (references.isDynamicMethod(definition) && references.isGeneratedMessageLiteBuilder(clazz)) {
       if (register.getAsBoolean()) {
-        assert builders.getOrDefault(clazz, context) == context;
-        builders.put(clazz, context);
+        assert !builders.containsKey(clazz) || builders.get(clazz).getDefinition() == definition;
+        builders.put(clazz, method);
         return true;
       }
     }
@@ -104,12 +105,11 @@
 
   private void removeDeadBuilderReferencesFromDynamicMethod(
       AppView<AppInfoWithLiveness> appView,
-      DexEncodedMethod dynamicMethod,
+      ProgramMethod dynamicMethod,
       IRConverter converter,
       CodeRewriter codeRewriter,
       SwitchCaseAnalyzer switchCaseAnalyzer) {
-    Origin origin = appView.appInfo().originFor(dynamicMethod.holder());
-    IRCode code = dynamicMethod.buildIR(appView, origin);
+    IRCode code = dynamicMethod.buildIR(appView);
     codeRewriter.rewriteSwitch(code, switchCaseAnalyzer);
     converter.removeDeadCodeAndFinalizeIR(
         dynamicMethod, code, OptimizationFeedbackSimple.getInstance(), Timing.empty());
@@ -132,7 +132,7 @@
     if (node != null) {
       List<Node> calleesToBeRemoved = new ArrayList<>();
       for (Node callee : node.getCalleesWithDeterministicOrder()) {
-        if (references.isDynamicMethodBridge(callee.method)) {
+        if (references.isDynamicMethodBridge(callee.getMethod())) {
           calleesToBeRemoved.add(callee);
         }
       }
@@ -143,7 +143,7 @@
   }
 
   public void inlineCallsToDynamicMethod(
-      DexEncodedMethod method,
+      ProgramMethod method,
       IRCode code,
       EnumValueOptimizer enumValueOptimizer,
       OptimizationFeedback feedback,
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index 4414507..7d74c63 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -9,10 +9,9 @@
 import static com.android.tools.r8.ir.analysis.proto.ProtoUtils.setObjectsValueForMessageInfoConstructionInvoke;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoMessageInfo;
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoObject;
 import com.android.tools.r8.ir.analysis.type.Nullability;
@@ -68,8 +67,9 @@
     this.stringType = TypeElement.stringClassType(appView, Nullability.definitelyNotNull());
   }
 
-  public void run(DexEncodedMethod method, IRCode code) {
-    if (references.isDynamicMethod(method.method)) {
+  public void run(IRCode code) {
+    ProgramMethod method = code.context();
+    if (references.isDynamicMethod(method.getReference())) {
       rewriteDynamicMethod(method, code);
     }
   }
@@ -89,19 +89,19 @@
     timing.end();
   }
 
-  private void forEachDynamicMethod(Consumer<DexEncodedMethod> consumer) {
+  private void forEachDynamicMethod(Consumer<ProgramMethod> consumer) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     appView
         .appInfo()
         .forEachInstantiatedSubType(
             references.generatedMessageLiteType,
             clazz -> {
-              DexMethod dynamicMethod =
+              DexMethod dynamicMethodReference =
                   dexItemFactory.createMethod(
                       clazz.type, references.dynamicMethodProto, references.dynamicMethodName);
-              DexEncodedMethod encodedDynamicMethod = clazz.lookupVirtualMethod(dynamicMethod);
-              if (encodedDynamicMethod != null) {
-                consumer.accept(encodedDynamicMethod);
+              ProgramMethod dynamicMethod = clazz.lookupProgramMethod(dynamicMethodReference);
+              if (dynamicMethod != null) {
+                consumer.accept(dynamicMethod);
               }
             },
             lambda -> {
@@ -117,12 +117,7 @@
    * <p>NOTE: This is work in progress. Understanding the full semantics of the arguments passed to
    * newMessageInfo is still pending.
    */
-  private void rewriteDynamicMethod(DexEncodedMethod method, IRCode code) {
-    DexClass context = appView.definitionFor(method.holder());
-    if (context == null || !context.isProgramClass()) {
-      return;
-    }
-
+  private void rewriteDynamicMethod(ProgramMethod method, IRCode code) {
     InvokeMethod newMessageInfoInvoke = getNewMessageInfoInvoke(code, references);
     if (newMessageInfoInvoke != null) {
       Value infoValue =
@@ -131,11 +126,10 @@
           getObjectsValueFromMessageInfoConstructionInvoke(newMessageInfoInvoke, references);
 
       // Decode the arguments passed to newMessageInfo().
-      ProtoMessageInfo protoMessageInfo = decoder.run(method, context, infoValue, objectsValue);
+      ProtoMessageInfo protoMessageInfo = decoder.run(method, infoValue, objectsValue);
       if (protoMessageInfo != null) {
         // Rewrite the arguments to newMessageInfo().
-        rewriteArgumentsToNewMessageInfo(
-            method, code, newMessageInfoInvoke, infoValue, protoMessageInfo);
+        rewriteArgumentsToNewMessageInfo(code, newMessageInfoInvoke, infoValue, protoMessageInfo);
 
         // Ensure that the definition of the original `objects` value is removed.
         IRCodeUtils.removeArrayAndTransitiveInputsIfNotUsed(code, objectsValue.definition);
@@ -147,13 +141,12 @@
   }
 
   private void rewriteArgumentsToNewMessageInfo(
-      DexEncodedMethod method,
       IRCode code,
       InvokeMethod newMessageInfoInvoke,
       Value infoValue,
       ProtoMessageInfo protoMessageInfo) {
     rewriteInfoArgumentToNewMessageInfo(code, infoValue, protoMessageInfo);
-    rewriteObjectsArgumentToNewMessageInfo(method, code, newMessageInfoInvoke, protoMessageInfo);
+    rewriteObjectsArgumentToNewMessageInfo(code, newMessageInfoInvoke, protoMessageInfo);
   }
 
   private void rewriteInfoArgumentToNewMessageInfo(
@@ -165,7 +158,6 @@
   }
 
   private void rewriteObjectsArgumentToNewMessageInfo(
-      DexEncodedMethod method,
       IRCode code,
       InvokeMethod newMessageInfoInvoke,
       ProtoMessageInfo protoMessageInfo) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
index 79dea35..d5fdc33 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
@@ -5,10 +5,9 @@
 package com.android.tools.r8.ir.analysis.proto;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoEnqueuerExtension;
 import com.android.tools.r8.shaking.DefaultEnqueuerUseRegistry;
 import com.android.tools.r8.shaking.Enqueuer;
@@ -21,11 +20,8 @@
   private final ProtoReferences references;
 
   public ProtoEnqueuerUseRegistry(
-      AppView<?> appView,
-      DexProgramClass currentHolder,
-      DexEncodedMethod currentMethod,
-      Enqueuer enqueuer) {
-    super(appView, currentHolder, currentMethod, enqueuer);
+      AppView<?> appView, ProgramMethod currentMethod, Enqueuer enqueuer) {
+    super(appView, currentMethod, enqueuer);
     this.references = appView.protoShrinker().references;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
index 1321984..ca4ca1e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
@@ -5,9 +5,8 @@
 package com.android.tools.r8.ir.analysis.proto;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.proto.ProtoReferences.MethodToInvokeMembers;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -35,9 +34,8 @@
 
   @Override
   public Reason computeInliningReason(
-      InvokeMethod invoke, DexEncodedMethod target, DexEncodedMethod context) {
-    DexProgramClass enclosingClass = appView.definitionFor(context.holder()).asProgramClass();
-    if (references.isAbstractGeneratedMessageLiteBuilder(enclosingClass)
+      InvokeMethod invoke, ProgramMethod target, ProgramMethod context) {
+    if (references.isAbstractGeneratedMessageLiteBuilder(context.getHolder())
         && invoke.isInvokeSuper()) {
       // Aggressively inline invoke-super calls inside the GeneratedMessageLite builders. Such
       // instructions prohibit inlining of the enclosing method into other contexts, and therefore
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 014ab85..c2c9a50 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
@@ -12,6 +12,7 @@
 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.ProgramMethod;
 import com.android.tools.r8.ir.code.Value;
 
 public class ProtoReferences {
@@ -43,6 +44,7 @@
   public final DexProto dynamicMethodProto;
   public final DexProto findLiteExtensionByNumberProto;
 
+  public final DexMethod dynamicMethod;
   public final DexMethod newMessageInfoMethod;
   public final DexMethod rawMessageInfoConstructor;
 
@@ -80,6 +82,8 @@
         factory.createProto(generatedExtensionType, messageLiteType, factory.intType);
 
     // Methods.
+    dynamicMethod =
+        factory.createMethod(generatedMessageLiteType, dynamicMethodProto, dynamicMethodName);
     newMessageInfoMethod =
         factory.createMethod(
             generatedMessageLiteType,
@@ -114,6 +118,10 @@
     return isDynamicMethod(encodedMethod.method);
   }
 
+  public boolean isDynamicMethod(ProgramMethod method) {
+    return isDynamicMethod(method.getReference());
+  }
+
   public boolean isDynamicMethodBridge(DexMethod method) {
     return method == generatedMessageLiteMethods.dynamicMethodBridgeMethod;
   }
@@ -122,6 +130,10 @@
     return isDynamicMethodBridge(method.method);
   }
 
+  public boolean isDynamicMethodBridge(ProgramMethod method) {
+    return isDynamicMethodBridge(method.getReference());
+  }
+
   public boolean isFindLiteExtensionByNumberMethod(DexMethod method) {
     return method.proto == findLiteExtensionByNumberProto
         && method.name.startsWith(findLiteExtensionByNumberName)
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
index 6d404cf..e4741d9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
@@ -7,12 +7,11 @@
 import static com.android.tools.r8.ir.analysis.proto.ProtoUtils.getInfoValueFromMessageInfoConstructionInvoke;
 import static com.android.tools.r8.ir.analysis.proto.ProtoUtils.getObjectsValueFromMessageInfoConstructionInvoke;
 
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.proto.schema.DeadProtoFieldObject;
 import com.android.tools.r8.ir.analysis.proto.schema.LiveProtoFieldObject;
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoFieldInfo;
@@ -76,16 +75,14 @@
     this.references = references;
   }
 
-  public ProtoMessageInfo run(
-      DexEncodedMethod dynamicMethod, DexClass context, InvokeMethod invoke) {
+  public ProtoMessageInfo run(ProgramMethod dynamicMethod, InvokeMethod invoke) {
     assert references.isMessageInfoConstructionMethod(invoke.getInvokedMethod());
     Value infoValue = getInfoValueFromMessageInfoConstructionInvoke(invoke, references);
     Value objectsValue = getObjectsValueFromMessageInfoConstructionInvoke(invoke, references);
-    return run(dynamicMethod, context, infoValue, objectsValue);
+    return run(dynamicMethod, infoValue, objectsValue);
   }
 
-  public ProtoMessageInfo run(
-      DexEncodedMethod dynamicMethod, DexClass context, Value infoValue, Value objectsValue) {
+  public ProtoMessageInfo run(ProgramMethod dynamicMethod, Value infoValue, Value objectsValue) {
     try {
       ProtoMessageInfo.Builder builder = ProtoMessageInfo.builder(dynamicMethod);
       ThrowingIntIterator<InvalidRawMessageInfoException> infoIterator =
@@ -125,13 +122,13 @@
       for (int i = 0; i < numberOfOneOfObjects; i++) {
         ProtoObject oneOfObject =
             createProtoObject(
-                objectIterator.computeNextIfAbsent(this::invalidObjectsFailure), context);
+                objectIterator.computeNextIfAbsent(this::invalidObjectsFailure), dynamicMethod);
         if (!oneOfObject.isProtoFieldObject()) {
           throw new InvalidRawMessageInfoException();
         }
         ProtoObject oneOfCaseObject =
             createProtoObject(
-                objectIterator.computeNextIfAbsent(this::invalidObjectsFailure), context);
+                objectIterator.computeNextIfAbsent(this::invalidObjectsFailure), dynamicMethod);
         if (!oneOfCaseObject.isProtoFieldObject()) {
           throw new InvalidRawMessageInfoException();
         }
@@ -142,7 +139,7 @@
       for (int i = 0; i < numberOfHasBitsObjects; i++) {
         ProtoObject hasBitsObject =
             createProtoObject(
-                objectIterator.computeNextIfAbsent(this::invalidObjectsFailure), context);
+                objectIterator.computeNextIfAbsent(this::invalidObjectsFailure), dynamicMethod);
         if (!hasBitsObject.isProtoFieldObject()) {
           throw new InvalidRawMessageInfoException();
         }
@@ -168,7 +165,7 @@
         try {
           List<ProtoObject> objects = new ArrayList<>(numberOfObjects);
           for (Value value : objectIterator.take(numberOfObjects)) {
-            objects.add(createProtoObject(value, context));
+            objects.add(createProtoObject(value, dynamicMethod));
           }
           builder.addField(new ProtoFieldInfo(fieldNumber, fieldType, auxData, objects));
         } catch (NoSuchElementException e) {
@@ -189,7 +186,7 @@
     }
   }
 
-  private ProtoObject createProtoObject(Value value, DexClass context)
+  private ProtoObject createProtoObject(Value value, ProgramMethod context)
       throws InvalidRawMessageInfoException {
     Value root = value.getAliasedValue();
     if (!root.isPhi()) {
@@ -199,13 +196,14 @@
         return new ProtoTypeObject(constClass.getValue());
       } else if (definition.isConstString()) {
         ConstString constString = definition.asConstString();
-        DexField field = context.lookupUniqueInstanceFieldWithName(constString.getValue());
+        DexField field =
+            context.getHolder().lookupUniqueInstanceFieldWithName(constString.getValue());
         if (field != null) {
           return new LiveProtoFieldObject(field);
         }
         // This const-string refers to a field that no longer exists. In this case, we create a
         // special dead-object instead of failing with an InvalidRawMessageInfoException below.
-        return new DeadProtoFieldObject(context.type, constString.getValue());
+        return new DeadProtoFieldObject(context.getHolderType(), constString.getValue());
       } else if (definition.isDexItemBasedConstString()) {
         DexItemBasedConstString constString = definition.asDexItemBasedConstString();
         DexReference reference = constString.getItem();
@@ -214,13 +212,13 @@
             && nameComputationInfo.isFieldNameComputationInfo()
             && nameComputationInfo.asFieldNameComputationInfo().isForFieldName()) {
           DexField field = reference.asDexField();
-          DexEncodedField encodedField = context.lookupInstanceField(field);
+          DexEncodedField encodedField = context.getHolder().lookupInstanceField(field);
           if (encodedField != null) {
             return new LiveProtoFieldObject(field);
           }
           // This const-string refers to a field that no longer exists. In this case, we create a
           // special dead-object instead of failing with an InvalidRawMessageInfoException below.
-          return new DeadProtoFieldObject(context.type, field.name);
+          return new DeadProtoFieldObject(context.getHolderType(), field.name);
         }
       } else if (definition.isInvokeStatic()) {
         InvokeStatic invoke = definition.asInvokeStatic();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index d6d2990..8001121 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -8,13 +8,13 @@
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteShrinker;
 import com.android.tools.r8.ir.analysis.proto.ProtoEnqueuerUseRegistry;
@@ -38,6 +38,7 @@
 import com.android.tools.r8.utils.BitUtils;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.IdentityHashMap;
@@ -79,7 +80,7 @@
       Sets.newIdentityHashSet();
 
   // The findLiteExtensionByNumber() methods that have become live since the last fixpoint.
-  private final Set<DexEncodedMethod> findLiteExtensionByNumberMethods = Sets.newIdentityHashSet();
+  private final ProgramMethodSet findLiteExtensionByNumberMethods = ProgramMethodSet.create();
 
   // Mapping from extension container types to the extensions for that type.
   private final Map<DexType, Set<DexType>> extensionGraph = new IdentityHashMap<>();
@@ -107,7 +108,7 @@
       assert clazz.type == references.extendableMessageType;
       return;
     }
-    DexEncodedMethod dynamicMethod = clazz.lookupVirtualMethod(references::isDynamicMethod);
+    ProgramMethod dynamicMethod = clazz.lookupProgramMethod(references.dynamicMethod);
     if (dynamicMethod != null) {
       worklist.enqueueMarkInstantiatedAction(
           clazz,
@@ -125,49 +126,39 @@
    * ProtoMessageInfo} object, and create a mapping from the holder to it.
    */
   @Override
-  public void processNewlyLiveMethod(DexEncodedMethod encodedMethod) {
-    if (references.isFindLiteExtensionByNumberMethod(encodedMethod.method)) {
-      findLiteExtensionByNumberMethods.add(encodedMethod);
+  public void processNewlyLiveMethod(ProgramMethod method) {
+    if (references.isFindLiteExtensionByNumberMethod(method.getReference())) {
+      findLiteExtensionByNumberMethods.add(method);
       return;
     }
 
-    if (!references.isDynamicMethod(encodedMethod)) {
+    if (!references.isDynamicMethod(method)) {
       return;
     }
 
-    DexType holder = encodedMethod.holder();
-    if (seenButNotLiveProtos.containsKey(holder)) {
+    DexType holderType = method.getHolderType();
+    if (seenButNotLiveProtos.containsKey(holderType)) {
       // The proto is now live instead of dead.
-      liveProtos.put(holder, seenButNotLiveProtos.remove(holder));
+      liveProtos.put(holderType, seenButNotLiveProtos.remove(holderType));
       return;
     }
 
     // Since this dynamicMethod() only becomes live once, and it has just become live, it must be
     // the case that the proto is not already live.
-    assert !liveProtos.containsKey(holder);
-    createProtoMessageInfoFromDynamicMethod(encodedMethod, liveProtos);
+    assert !liveProtos.containsKey(holderType);
+    createProtoMessageInfoFromDynamicMethod(method, liveProtos);
   }
 
   private void createProtoMessageInfoFromDynamicMethod(
-      DexEncodedMethod dynamicMethod, Map<DexType, ProtoMessageInfo> protos) {
-    DexType holder = dynamicMethod.holder();
+      ProgramMethod dynamicMethod, Map<DexType, ProtoMessageInfo> protos) {
+    DexType holder = dynamicMethod.getHolderType();
     assert !protos.containsKey(holder);
 
-    DexClass context = appView.definitionFor(holder);
-    if (context == null || !context.isProgramClass()) {
-      // TODO(b/112437944): What if a proto message references a proto message on the classpath or
-      //  library path? We should treat them as having a map/required field to be conservative.
-      assert false; // Should generally not happen.
-      return;
-    }
-
-    IRCode code = dynamicMethod.buildIR(appView, context.origin);
+    IRCode code = dynamicMethod.buildIR(appView);
     InvokeMethod newMessageInfoInvoke =
         GeneratedMessageLiteShrinker.getNewMessageInfoInvoke(code, references);
     ProtoMessageInfo protoMessageInfo =
-        newMessageInfoInvoke != null
-            ? decoder.run(dynamicMethod, context, newMessageInfoInvoke)
-            : null;
+        newMessageInfoInvoke != null ? decoder.run(dynamicMethod, newMessageInfoInvoke) : null;
     protos.put(holder, protoMessageInfo);
   }
 
@@ -221,13 +212,13 @@
     collectExtensionFields()
         .forEach(
             (clazz, extensionFields) -> {
-              DexEncodedMethod clinit = clazz.getClassInitializer();
+              ProgramMethod clinit = clazz.getProgramClassInitializer();
               if (clinit == null) {
                 assert false; // Should generally not happen.
                 return;
               }
 
-              IRCode code = clinit.buildIR(appView, appView.appInfo().originFor(clazz.type));
+              IRCode code = clinit.buildIR(appView);
               Map<DexEncodedField, StaticPut> uniqueStaticPuts =
                   IRCodeUtils.findUniqueStaticPuts(appView, code, extensionFields);
               for (DexEncodedField extensionField : extensionFields) {
@@ -251,10 +242,8 @@
    */
   private Map<DexProgramClass, Set<DexEncodedField>> collectExtensionFields() {
     Map<DexProgramClass, Set<DexEncodedField>> extensionFieldsByClass = new IdentityHashMap<>();
-    for (DexEncodedMethod findLiteExtensionByNumberMethod : findLiteExtensionByNumberMethods) {
-      IRCode code =
-          findLiteExtensionByNumberMethod.buildIR(
-              appView, appView.appInfo().originFor(findLiteExtensionByNumberMethod.holder()));
+    for (ProgramMethod findLiteExtensionByNumberMethod : findLiteExtensionByNumberMethods) {
+      IRCode code = findLiteExtensionByNumberMethod.buildIR(appView);
       for (BasicBlock block : code.blocks(BasicBlock::isReturnBlock)) {
         Value returnValue = block.exit().asReturn().returnValue().getAliasedValue();
         if (returnValue.isPhi()) {
@@ -364,9 +353,7 @@
         continue;
       }
 
-      DexEncodedMethod dynamicMethod = protoMessageInfo.getDynamicMethod();
-      DexProgramClass clazz = appView.definitionFor(dynamicMethod.holder()).asProgramClass();
-
+      ProgramMethod dynamicMethod = protoMessageInfo.getDynamicMethod();
       for (ProtoFieldInfo protoFieldInfo : protoMessageInfo.getFields()) {
         DexEncodedField valueStorage = protoFieldInfo.getValueStorage(appView, protoMessageInfo);
         if (valueStorage == null) {
@@ -390,7 +377,7 @@
           // written such that we cannot optimize any field reads or writes.
           enqueuer.registerReflectiveFieldAccess(valueStorage.field, dynamicMethod);
           worklist.enqueueMarkReachableFieldAction(
-              clazz, valueStorage, KeepReason.reflectiveUseIn(dynamicMethod));
+              dynamicMethod.getHolder(), valueStorage, KeepReason.reflectiveUseIn(dynamicMethod));
           valueStorageIsLive = true;
         } else {
           valueStorageIsLive = false;
@@ -438,10 +425,13 @@
         if (newlyLiveField != null) {
           // Mark hazzer and one-of proto fields as read from dynamicMethod() if they are written in
           // the app. This is needed to ensure that field writes are not removed from the app.
-          DexEncodedMethod defaultInitializer = clazz.getDefaultInitializer();
+          ProgramMethod defaultInitializer =
+              dynamicMethod.getHolder().getProgramDefaultInitializer();
           assert defaultInitializer != null;
           Predicate<DexEncodedMethod> neitherDefaultConstructorNorDynamicMethod =
-              writer -> writer != defaultInitializer && writer != dynamicMethod;
+              writer ->
+                  writer != defaultInitializer.getDefinition()
+                      && writer != dynamicMethod.getDefinition();
           if (enqueuer.isFieldWrittenInMethodSatisfying(
               newlyLiveField, neitherDefaultConstructorNorDynamicMethod)) {
             enqueuer.registerReflectiveFieldRead(newlyLiveField.field, dynamicMethod);
@@ -451,7 +441,9 @@
           // dynamicMethod().
           if (enqueuer.registerReflectiveFieldWrite(newlyLiveField.field, dynamicMethod)) {
             worklist.enqueueMarkReachableFieldAction(
-                clazz, newlyLiveField, KeepReason.reflectiveUseIn(dynamicMethod));
+                dynamicMethod.getHolder(),
+                newlyLiveField,
+                KeepReason.reflectiveUseIn(dynamicMethod));
           }
         }
       }
@@ -467,8 +459,8 @@
         continue;
       }
 
-      DexEncodedMethod dynamicMethod = protoMessageInfo.getDynamicMethod();
-      if (!dynamicMethodsWithTracedProtoObjects.add(dynamicMethod)) {
+      ProgramMethod dynamicMethod = protoMessageInfo.getDynamicMethod();
+      if (!dynamicMethodsWithTracedProtoObjects.add(dynamicMethod.getDefinition())) {
         continue;
       }
 
@@ -526,13 +518,14 @@
       return;
     }
 
-    DexClass clazz = appView.definitionFor(encodedOneOfCaseField.holder());
-    if (clazz == null || !clazz.isProgramClass()) {
+    DexProgramClass clazz =
+        asProgramClassOrNull(appView.definitionFor(encodedOneOfCaseField.holder()));
+    if (clazz == null) {
       assert false;
       return;
     }
 
-    DexEncodedMethod dynamicMethod = clazz.lookupVirtualMethod(references::isDynamicMethod);
+    ProgramMethod dynamicMethod = clazz.lookupProgramMethod(references.dynamicMethod);
     if (dynamicMethod == null) {
       assert false;
       return;
@@ -651,13 +644,13 @@
       return seenButNotLiveProtos.get(type);
     }
 
-    DexClass clazz = appView.definitionFor(type);
-    if (clazz == null || !clazz.isProgramClass()) {
+    DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+    if (clazz == null) {
       seenButNotLiveProtos.put(type, null);
       return null;
     }
 
-    DexEncodedMethod dynamicMethod = clazz.lookupVirtualMethod(references::isDynamicMethod);
+    ProgramMethod dynamicMethod = clazz.lookupProgramMethod(references.dynamicMethod);
     if (dynamicMethod == null) {
       seenButNotLiveProtos.put(type, null);
       return null;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java
index f026295..28de572 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.ir.analysis.proto.schema;
 
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.proto.ProtoUtils;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
@@ -22,7 +22,7 @@
 
   public static class Builder {
 
-    private final DexEncodedMethod dynamicMethod;
+    private final ProgramMethod dynamicMethod;
 
     private int flags;
 
@@ -30,7 +30,7 @@
     private LinkedList<ProtoFieldObject> hasBitsObjects;
     private LinkedList<ProtoOneOfObjectPair> oneOfObjects;
 
-    private Builder(DexEncodedMethod dynamicMethod) {
+    private Builder(ProgramMethod dynamicMethod) {
       this.dynamicMethod = dynamicMethod;
     }
 
@@ -171,7 +171,7 @@
     }
   }
 
-  private final DexEncodedMethod dynamicMethod;
+  private final ProgramMethod dynamicMethod;
   private final int flags;
 
   private final LinkedList<ProtoFieldInfo> fields;
@@ -179,7 +179,7 @@
   private final LinkedList<ProtoOneOfObjectPair> oneOfObjects;
 
   private ProtoMessageInfo(
-      DexEncodedMethod dynamicMethod,
+      ProgramMethod dynamicMethod,
       int flags,
       LinkedList<ProtoFieldInfo> fields,
       LinkedList<ProtoFieldObject> hasBitsObjects,
@@ -191,7 +191,7 @@
     this.oneOfObjects = oneOfObjects;
   }
 
-  public static ProtoMessageInfo.Builder builder(DexEncodedMethod dynamicMethod) {
+  public static ProtoMessageInfo.Builder builder(ProgramMethod dynamicMethod) {
     return new ProtoMessageInfo.Builder(dynamicMethod);
   }
 
@@ -199,7 +199,7 @@
     return ProtoUtils.isProto2(flags);
   }
 
-  public DexEncodedMethod getDynamicMethod() {
+  public ProgramMethod getDynamicMethod() {
     return dynamicMethod;
   }
 
@@ -220,7 +220,7 @@
   }
 
   public DexType getType() {
-    return dynamicMethod.holder();
+    return dynamicMethod.getHolderType();
   }
 
   public boolean hasFields() {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
index b45ec56..76213c6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.IRCode;
@@ -79,16 +80,15 @@
   }
 
   @Override
-  public boolean isMaterializableInContext(AppView<AppInfoWithLiveness> appView, DexType context) {
+  public boolean isMaterializableInContext(
+      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
     DexType baseType = type.toBaseType(appView.dexItemFactory());
     if (baseType.isClassType()) {
       DexClass clazz = appView.definitionFor(type);
       return clazz != null
           && clazz.isResolvable(appView)
           && AccessControl.isClassAccessible(
-                  clazz,
-                  appView.definitionFor(context).asProgramClass(),
-                  appView.options().featureSplitConfiguration)
+                  clazz, context.getHolder(), appView.options().featureSplitConfiguration)
               .isTrue();
     }
     assert baseType.isPrimitiveType();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index 46261f5..7bccd30 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.IRCode;
@@ -77,11 +78,12 @@
   }
 
   @Override
-  public boolean isMaterializableInContext(AppView<AppInfoWithLiveness> appView, DexType context) {
+  public boolean isMaterializableInContext(
+      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
     return AccessControl.isFieldAccessible(
             appView.appInfo().resolveField(field),
             appView.definitionForHolder(field),
-            appView.definitionFor(context).asProgramClass(),
+            context.getHolder(),
             appView.appInfo())
         .isTrue();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
index 1982181..67a0858 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -7,8 +7,8 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
@@ -80,7 +80,8 @@
   }
 
   @Override
-  public boolean isMaterializableInContext(AppView<AppInfoWithLiveness> appView, DexType context) {
+  public boolean isMaterializableInContext(
+      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
index 01f80cc..457d49b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
@@ -11,8 +11,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstString;
@@ -81,7 +81,8 @@
   }
 
   @Override
-  public boolean isMaterializableInContext(AppView<AppInfoWithLiveness> appView, DexType context) {
+  public boolean isMaterializableInContext(
+      AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
index 61426a0..f0425b0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
@@ -6,8 +6,8 @@
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
@@ -41,7 +41,7 @@
       TypeAndLocalInfoSupplier info);
 
   public abstract boolean isMaterializableInContext(
-      AppView<AppInfoWithLiveness> appView, DexType context);
+      AppView<AppInfoWithLiveness> appView, ProgramMethod context);
 
   public abstract boolean isMaterializableInAllContexts(AppView<?> appView);
 
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 53b9336..a37e369 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
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.TypeChecker;
 import com.android.tools.r8.ir.analysis.ValueMayDependOnEnvironmentAnalysis;
@@ -104,7 +105,7 @@
   // use odd instruction numbers for the insertion of moves during spilling.
   public static final int INSTRUCTION_NUMBER_DELTA = 2;
 
-  private final DexEncodedMethod method;
+  private final ProgramMethod method;
 
   public LinkedList<BasicBlock> blocks;
   public final ValueNumberGenerator valueNumberGenerator;
@@ -124,7 +125,7 @@
 
   public IRCode(
       InternalOptions options,
-      DexEncodedMethod method,
+      ProgramMethod method,
       LinkedList<BasicBlock> blocks,
       ValueNumberGenerator valueNumberGenerator,
       IRMetadata metadata,
@@ -145,10 +146,15 @@
     return metadata;
   }
 
-  public DexEncodedMethod method() {
+  public ProgramMethod context() {
     return method;
   }
 
+  @Deprecated
+  public DexEncodedMethod method() {
+    return method.getDefinition();
+  }
+
   public BasicBlock entryBlock() {
     return blocks.getFirst();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 78d3a69..6c9fb6f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
@@ -78,6 +79,11 @@
   public abstract DexEncodedMethod lookupSingleTarget(
       AppView<?> appView, DexType invocationContext);
 
+  public final ProgramMethod lookupSingleProgramTarget(AppView<?> appView, ProgramMethod context) {
+    DexEncodedMethod singleTarget = lookupSingleTarget(appView, context.getHolderType());
+    return singleTarget != null ? singleTarget.asProgramMethod(appView) : null;
+  }
+
   // TODO(b/140204899): Refactor lookup methods to be defined in a single place.
   public Collection<DexEncodedMethod> lookupTargets(
       AppView<AppInfoWithLiveness> appView, DexType invocationContext) {
@@ -135,7 +141,7 @@
   }
 
   public abstract InlineAction computeInlining(
-      DexEncodedMethod singleTarget,
+      ProgramMethod singleTarget,
       Reason reason,
       DefaultInliningOracle decider,
       ClassInitializationAnalysis classInitializationAnalysis,
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 5ed1539..00ffbe1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -5,10 +5,10 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -43,7 +43,7 @@
 
   @Override
   public final InlineAction computeInlining(
-      DexEncodedMethod singleTarget,
+      ProgramMethod singleTarget,
       Reason reason,
       DefaultInliningOracle decider,
       ClassInitializationAnalysis classInitializationAnalysis,
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index 41b1124..8d46da1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -139,7 +140,7 @@
 
   @Override
   public InlineAction computeInlining(
-      DexEncodedMethod singleTarget,
+      ProgramMethod singleTarget,
       Reason reason,
       DefaultInliningOracle decider,
       ClassInitializationAnalysis classInitializationAnalysis,
@@ -148,7 +149,7 @@
     if (singleTarget != null) {
       throw new Unreachable(
           "Unexpected invoke-polymorphic with `"
-              + singleTarget.method.toSourceString()
+              + singleTarget.toSourceString()
               + "` as single target");
     }
     throw new Unreachable("Unexpected attempt to inline invoke that does not have a single target");
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 9a9f727..5ea6e46 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
@@ -130,7 +131,7 @@
 
   @Override
   public InlineAction computeInlining(
-      DexEncodedMethod singleTarget,
+      ProgramMethod singleTarget,
       Reason reason,
       DefaultInliningOracle decider,
       ClassInitializationAnalysis classInitializationAnalysis,
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java
index 2b05e0c..6ed94b4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Position.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -4,9 +4,9 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.google.common.annotations.VisibleForTesting;
 import java.util.Objects;
 
@@ -79,15 +79,15 @@
   }
 
   public static Position getPositionForInlining(
-      AppView<?> appView, InvokeMethod invoke, DexEncodedMethod context) {
+      AppView<?> appView, InvokeMethod invoke, ProgramMethod context) {
     Position position = invoke.getPosition();
     if (position.method == null) {
       assert position.isNone();
-      position = Position.noneWithMethod(context.method, null);
+      position = Position.noneWithMethod(context.getReference(), null);
     }
     assert position.callerPosition == null
         || position.getOutermostCaller().method
-            == appView.graphLense().getOriginalMethodSignature(context.method);
+            == appView.graphLense().getOriginalMethodSignature(context.getReference());
     return position;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 6f3bf92..a5842de 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -6,9 +6,11 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator.CycleEliminationResult;
 import com.android.tools.r8.ir.conversion.CallSiteInformation.CallGraphBasedCallSiteInformation;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
 import java.util.Iterator;
 import java.util.Set;
@@ -38,7 +40,7 @@
 
     public static Node[] EMPTY_ARRAY = {};
 
-    public final DexEncodedMethod method;
+    private final ProgramMethod method;
     private int numberOfCallSites = 0;
 
     // Outgoing calls from this method.
@@ -55,7 +57,7 @@
     // by the current method).
     private final Set<Node> writers = new TreeSet<>();
 
-    public Node(DexEncodedMethod method) {
+    public Node(ProgramMethod method) {
       this.method = method;
     }
 
@@ -201,14 +203,16 @@
 
     @Override
     public int compareTo(Node other) {
-      return method.method.slowCompareTo(other.method.method);
+      return getProgramMethod()
+          .getReference()
+          .slowCompareTo(other.getProgramMethod().getReference());
     }
 
     @Override
     public String toString() {
       StringBuilder builder = new StringBuilder();
       builder.append("MethodNode for: ");
-      builder.append(method.toSourceString());
+      builder.append(getProgramMethod().toSourceString());
       builder.append(" (");
       builder.append(callees.size());
       builder.append(" callees, ");
@@ -222,7 +226,7 @@
         builder.append(System.lineSeparator());
         for (Node call : callees) {
           builder.append("  ");
-          builder.append(call.method.toSourceString());
+          builder.append(call.getProgramMethod().toSourceString());
           builder.append(System.lineSeparator());
         }
       }
@@ -231,12 +235,20 @@
         builder.append(System.lineSeparator());
         for (Node caller : callers) {
           builder.append("  ");
-          builder.append(caller.method.toSourceString());
+          builder.append(caller.getProgramMethod().toSourceString());
           builder.append(System.lineSeparator());
         }
       }
       return builder.toString();
     }
+
+    public DexEncodedMethod getMethod() {
+      return method.getDefinition();
+    }
+
+    public ProgramMethod getProgramMethod() {
+      return method;
+    }
   }
 
   final Set<Node> nodes;
@@ -266,22 +278,22 @@
     return nodes.isEmpty();
   }
 
-  public Set<DexEncodedMethod> extractLeaves() {
+  public ProgramMethodSet extractLeaves() {
     return extractNodes(Node::isLeaf, Node::cleanCallersAndReadersForRemoval);
   }
 
-  public Set<DexEncodedMethod> extractRoots() {
+  public ProgramMethodSet extractRoots() {
     return extractNodes(Node::isRoot, Node::cleanCalleesAndWritersForRemoval);
   }
 
-  private Set<DexEncodedMethod> extractNodes(Predicate<Node> predicate, Consumer<Node> clean) {
-    Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
+  private ProgramMethodSet extractNodes(Predicate<Node> predicate, Consumer<Node> clean) {
+    ProgramMethodSet result = ProgramMethodSet.create();
     Set<Node> removed = Sets.newIdentityHashSet();
     Iterator<Node> nodeIterator = nodes.iterator();
     while (nodeIterator.hasNext()) {
       Node node = nodeIterator.next();
       if (predicate.test(node)) {
-        result.add(node.method);
+        result.add(node.getProgramMethod());
         nodeIterator.remove();
         removed.add(node);
       }
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 5b94efb..e6f7cb6 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
@@ -4,12 +4,14 @@
 
 package com.android.tools.r8.ir.conversion;
 
+import static com.google.common.base.Predicates.alwaysTrue;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ThreadUtils;
-import com.google.common.base.Predicates;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
@@ -25,21 +27,18 @@
   }
 
   private void processClass(DexProgramClass clazz) {
-    clazz.forEachMethod(this::processMethod);
+    clazz.forEachProgramMethodMatching(DexEncodedMethod::hasCode, this::processMethod);
   }
 
-  private void processMethod(DexEncodedMethod method) {
-    if (method.hasCode()) {
-      method.registerCodeReferences(
-          new InvokeExtractor(getOrCreateNode(method), Predicates.alwaysTrue()));
-    }
+  private void processMethod(ProgramMethod method) {
+    method.registerCodeReferences(new InvokeExtractor(getOrCreateNode(method), alwaysTrue()));
   }
 
   @Override
   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.method) != 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 49ea06a..e6bd8bf 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
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -19,6 +20,7 @@
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
 import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.Invoke;
@@ -26,15 +28,13 @@
 import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator.CycleEliminationResult;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableSet;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Deque;
-import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
@@ -52,7 +52,7 @@
   final AppView<AppInfoWithLiveness> appView;
   private final FieldAccessInfoCollection<?> fieldAccessInfoCollection;
   final Map<DexMethod, Node> nodes = new IdentityHashMap<>();
-  private final Map<DexMethod, Set<DexEncodedMethod>> possibleTargetsCache =
+  private final Map<DexMethod, ProgramMethodSet> possibleProgramTargetsCache =
       new ConcurrentHashMap<>();
 
   CallGraphBuilderBase(AppView<AppInfoWithLiveness> appView) {
@@ -97,9 +97,9 @@
     return true;
   }
 
-  Node getOrCreateNode(DexEncodedMethod method) {
+  Node getOrCreateNode(ProgramMethod method) {
     synchronized (nodes) {
-      return nodes.computeIfAbsent(method.method, ignore -> new Node(method));
+      return nodes.computeIfAbsent(method.getReference(), ignore -> new Node(method));
     }
   }
 
@@ -108,9 +108,9 @@
   class InvokeExtractor extends UseRegistry {
 
     private final Node currentMethod;
-    private final Predicate<DexEncodedMethod> targetTester;
+    private final Predicate<ProgramMethod> targetTester;
 
-    InvokeExtractor(Node currentMethod, Predicate<DexEncodedMethod> targetTester) {
+    InvokeExtractor(Node currentMethod, Predicate<ProgramMethod> targetTester) {
       super(appView.dexItemFactory());
       this.currentMethod = currentMethod;
       this.targetTester = targetTester;
@@ -119,7 +119,7 @@
     private void addClassInitializerTarget(DexProgramClass clazz) {
       assert clazz != null;
       if (clazz.hasClassInitializer()) {
-        addCallEdge(clazz.getClassInitializer(), false);
+        addCallEdge(clazz.getProgramClassInitializer(), false);
       }
     }
 
@@ -131,36 +131,42 @@
       }
     }
 
-    private void addCallEdge(DexEncodedMethod callee, boolean likelySpuriousCallEdge) {
+    private void addCallEdge(ProgramMethod callee, boolean likelySpuriousCallEdge) {
       if (!targetTester.test(callee)) {
         return;
       }
-      if (callee.accessFlags.isAbstract()) {
+      if (callee.getDefinition().isAbstract()) {
         // Not a valid target.
         return;
       }
-      if (appView.appInfo().isPinned(callee.method)) {
+      if (callee.getDefinition().isNative()) {
+        // We don't care about calls to native methods.
+        return;
+      }
+      if (appView.appInfo().isPinned(callee.getReference())) {
         // Since the callee is kept, we cannot inline it into the caller, and we also cannot collect
         // any optimization info for the method. Therefore, we drop the call edge to reduce the
         // total number of call graph edges, which should lead to fewer call graph cycles.
         return;
       }
-      assert callee.isProgramMethod(appView);
       getOrCreateNode(callee).addCallerConcurrently(currentMethod, likelySpuriousCallEdge);
     }
 
     private void addFieldReadEdge(DexEncodedMethod writer) {
-      assert !writer.accessFlags.isAbstract();
+      addFieldReadEdge(writer.asProgramMethod(appView));
+    }
+
+    private void addFieldReadEdge(ProgramMethod writer) {
+      assert !writer.getDefinition().isAbstract();
       if (!targetTester.test(writer)) {
         return;
       }
-      assert writer.isProgramMethod(appView);
       getOrCreateNode(writer).addReaderConcurrently(currentMethod);
     }
 
     private void processInvoke(Invoke.Type originalType, DexMethod originalMethod) {
-      DexEncodedMethod source = currentMethod.method;
-      DexMethod context = source.method;
+      ProgramMethod source = currentMethod.getProgramMethod();
+      DexMethod context = source.getReference();
       GraphLenseLookupResult result =
           appView.graphLense().lookupMethod(originalMethod, context, originalType);
       DexMethod method = result.getMethod();
@@ -173,19 +179,16 @@
           processInvokeWithDynamicDispatch(type, target, context.holder);
         }
       } else {
-        DexEncodedMethod singleTarget =
-            appView.appInfo().lookupSingleTarget(type, method, context.holder, appView);
+        ProgramMethod singleTarget =
+            appView.appInfo().lookupSingleProgramTarget(type, method, context.holder, appView);
         if (singleTarget != null) {
-          assert !source.accessFlags.isBridge() || singleTarget != currentMethod.method;
-          DexProgramClass clazz =
-              asProgramClassOrNull(appView.definitionFor(singleTarget.holder()));
-          if (clazz != null) {
-            // For static invokes, the class could be initialized.
-            if (type == Invoke.Type.STATIC) {
-              addClassInitializerTarget(clazz);
-            }
-            addCallEdge(singleTarget, false);
+          assert !source.getDefinition().isBridge()
+              || singleTarget.getDefinition() != source.getDefinition();
+          // For static invokes, the class could be initialized.
+          if (type == Invoke.Type.STATIC) {
+            addClassInitializerTarget(singleTarget.getHolder());
           }
+          addCallEdge(singleTarget, false);
         }
       }
     }
@@ -207,8 +210,8 @@
       }
 
       boolean isInterface = type == Invoke.Type.INTERFACE;
-      Set<DexEncodedMethod> possibleTargets =
-          possibleTargetsCache.computeIfAbsent(
+      ProgramMethodSet possibleProgramTargets =
+          possibleProgramTargetsCache.computeIfAbsent(
               target,
               method -> {
                 ResolutionResult resolution =
@@ -218,27 +221,34 @@
                       resolution.lookupVirtualDispatchTargets(
                           appView.definitionForProgramType(context), appView.appInfo());
                   if (lookupResult.isLookupResultSuccess()) {
-                    Set<DexEncodedMethod> targets = new HashSet<>();
+                    ProgramMethodSet targets = ProgramMethodSet.create();
                     lookupResult
                         .asLookupResultSuccess()
                         .forEach(
-                            methodTarget -> targets.add(methodTarget.getDefinition()),
-                            lambdaTarget ->
-                                // The call target will ultimately be the implementation method.
-                                targets.add(
-                                    lambdaTarget.getImplementationMethod().getDefinition()));
+                            methodTarget -> {
+                              if (methodTarget.isProgramMethod()) {
+                                targets.add(methodTarget.asProgramMethod());
+                              }
+                            },
+                            lambdaTarget -> {
+                              // The call target will ultimately be the implementation method.
+                              DexClassAndMethod implementationMethod =
+                                  lambdaTarget.getImplementationMethod();
+                              if (implementationMethod.isProgramMethod()) {
+                                targets.add(implementationMethod.asProgramMethod());
+                              }
+                            });
                     return targets;
                   }
                 }
                 return null;
               });
-      if (possibleTargets != null) {
+      if (possibleProgramTargets != null) {
         boolean likelySpuriousCallEdge =
-            possibleTargets.size() >= appView.options().callGraphLikelySpuriousCallEdgeThreshold;
-        for (DexEncodedMethod possibleTarget : possibleTargets) {
-          if (possibleTarget.isProgramMethod(appView)) {
-            addCallEdge(possibleTarget, likelySpuriousCallEdge);
-          }
+            possibleProgramTargets.size()
+                >= appView.options().callGraphLikelySpuriousCallEdgeThreshold;
+        for (ProgramMethod possibleTarget : possibleProgramTargets) {
+          addCallEdge(possibleTarget, likelySpuriousCallEdge);
         }
       }
     }
@@ -401,19 +411,19 @@
 
     static class CycleEliminationResult {
 
-      private Map<DexEncodedMethod, Set<DexEncodedMethod>> removedCallEdges;
+      private Map<DexEncodedMethod, ProgramMethodSet> removedCallEdges;
 
-      CycleEliminationResult(Map<DexEncodedMethod, Set<DexEncodedMethod>> removedCallEdges) {
+      CycleEliminationResult(Map<DexEncodedMethod, ProgramMethodSet> removedCallEdges) {
         this.removedCallEdges = removedCallEdges;
       }
 
-      void forEachRemovedCaller(DexEncodedMethod callee, Consumer<DexEncodedMethod> fn) {
-        removedCallEdges.getOrDefault(callee, ImmutableSet.of()).forEach(fn);
+      void forEachRemovedCaller(ProgramMethod callee, Consumer<ProgramMethod> fn) {
+        removedCallEdges.getOrDefault(callee.getDefinition(), ProgramMethodSet.empty()).forEach(fn);
       }
 
       int numberOfRemovedCallEdges() {
         int numberOfRemovedCallEdges = 0;
-        for (Set<DexEncodedMethod> nodes : removedCallEdges.values()) {
+        for (ProgramMethodSet nodes : removedCallEdges.values()) {
           numberOfRemovedCallEdges += nodes.size();
         }
         return numberOfRemovedCallEdges;
@@ -445,7 +455,7 @@
     private Map<Node, Set<Node>> writersToBeRemoved = new IdentityHashMap<>();
 
     // Mapping from callee to the set of callers that were removed from the callee.
-    private Map<DexEncodedMethod, Set<DexEncodedMethod>> removedCallEdges = new IdentityHashMap<>();
+    private Map<DexEncodedMethod, ProgramMethodSet> removedCallEdges = new IdentityHashMap<>();
 
     // Set of nodes from which cycle elimination must be rerun to ensure that all cycles will be
     // removed.
@@ -747,13 +757,13 @@
       // All call edges where the callee is a method that should be force inlined must be kept,
       // to guarantee that the IR converter will process the callee before the caller.
       assert calleeOrWriter.hasCaller(callerOrReader);
-      return !calleeOrWriter.method.getOptimizationInfo().forceInline();
+      return !calleeOrWriter.getMethod().getOptimizationInfo().forceInline();
     }
 
     private void recordCallEdgeRemoval(Node caller, Node callee) {
       removedCallEdges
-          .computeIfAbsent(callee.method, ignore -> SetUtils.newIdentityHashSet(2))
-          .add(caller.method);
+          .computeIfAbsent(callee.getMethod(), ignore -> ProgramMethodSet.create(2))
+          .add(caller.getProgramMethod());
     }
 
     private void recoverStack(LinkedList<Node> extractedCycle) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java b/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
index 937b0ca..993315b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
@@ -4,8 +4,8 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
@@ -15,13 +15,15 @@
 
   /**
    * Check if the <code>method</code> is guaranteed to only have a single call site.
-   * <p>
-   * For pinned methods (methods kept through Proguard keep rules) this will always answer
-   * <code>false</code>.
+   *
+   * <p>For pinned methods (methods kept through Proguard keep rules) this will always answer <code>
+   * false</code>.
+   *
+   * @param method
    */
-  public abstract boolean hasSingleCallSite(DexMethod method);
+  public abstract boolean hasSingleCallSite(ProgramMethod method);
 
-  public abstract boolean hasDoubleCallSite(DexMethod method);
+  public abstract boolean hasDoubleCallSite(ProgramMethod method);
 
   public static CallSiteInformation empty() {
     return EmptyCallSiteInformation.EMPTY_INFO;
@@ -32,12 +34,12 @@
     private static final EmptyCallSiteInformation EMPTY_INFO = new EmptyCallSiteInformation();
 
     @Override
-    public boolean hasSingleCallSite(DexMethod method) {
+    public boolean hasSingleCallSite(ProgramMethod method) {
       return false;
     }
 
     @Override
-    public boolean hasDoubleCallSite(DexMethod method) {
+    public boolean hasDoubleCallSite(ProgramMethod method) {
       return false;
     }
   }
@@ -49,25 +51,25 @@
 
     CallGraphBasedCallSiteInformation(AppView<AppInfoWithLiveness> appView, CallGraph graph) {
       for (Node node : graph.nodes) {
-        DexEncodedMethod encodedMethod = node.method;
-        DexMethod method = encodedMethod.method;
+        ProgramMethod method = node.getProgramMethod();
+        DexMethod reference = method.getReference();
 
         // For non-pinned methods and methods that override library methods we do not know the exact
         // number of call sites.
-        if (appView.appInfo().isPinned(method)) {
+        if (appView.appInfo().isPinned(reference)) {
           continue;
         }
 
         if (appView.options().disableInliningOfLibraryMethodOverrides
-            && encodedMethod.isLibraryMethodOverride().isTrue()) {
+            && method.getDefinition().isLibraryMethodOverride().isTrue()) {
           continue;
         }
 
         int numberOfCallSites = node.getNumberOfCallSites();
         if (numberOfCallSites == 1) {
-          singleCallSite.add(method);
+          singleCallSite.add(reference);
         } else if (numberOfCallSites == 2) {
-          doubleCallSite.add(method);
+          doubleCallSite.add(reference);
         }
       }
     }
@@ -79,8 +81,8 @@
      * library method this always returns false.
      */
     @Override
-    public boolean hasSingleCallSite(DexMethod method) {
-      return singleCallSite.contains(method);
+    public boolean hasSingleCallSite(ProgramMethod method) {
+      return singleCallSite.contains(method.getReference());
     }
 
     /**
@@ -90,8 +92,8 @@
      * library method this always returns false.
      */
     @Override
-    public boolean hasDoubleCallSite(DexMethod method) {
-      return doubleCallSite.contains(method);
+    public boolean hasDoubleCallSite(ProgramMethod method) {
+      return doubleCallSite.contains(method.getReference());
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index f13653a..d4c8040 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -26,6 +26,7 @@
 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.ProgramMethod;
 import com.android.tools.r8.ir.code.CanonicalPositions;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Monitor;
@@ -201,7 +202,7 @@
   private CfState state;
   private final List<CfCode.LocalVariableInfo> localVariables;
   private final CfCode code;
-  private final DexEncodedMethod method;
+  private final ProgramMethod method;
   private final Origin origin;
   private final AppView<?> appView;
 
@@ -219,7 +220,7 @@
   public CfSourceCode(
       CfCode code,
       List<CfCode.LocalVariableInfo> localVariables,
-      DexEncodedMethod method,
+      ProgramMethod method,
       DexMethod originalMethod,
       Position callerPosition,
       Origin origin,
@@ -244,9 +245,13 @@
     internalOutputMode = appView.options().getInternalOutputMode();
 
     needsGeneratedMethodSynchronization =
-        !method.isProcessed()
+        !getMethod().isProcessed()
             && internalOutputMode.isGeneratingDex()
-            && method.accessFlags.isSynchronized();
+            && getMethod().isSynchronized();
+  }
+
+  private DexEncodedMethod getMethod() {
+    return method.getDefinition();
   }
 
   public Origin getOrigin() {
@@ -385,15 +390,15 @@
     inPrelude = true;
     state.buildPrelude(canonicalPositions.getPreamblePosition());
     setLocalVariableLists();
-    builder.buildArgumentsWithRewrittenPrototypeChanges(0, method, state::write);
+    builder.buildArgumentsWithRewrittenPrototypeChanges(0, getMethod(), state::write);
     // Add debug information for all locals at the initial label.
     Int2ReferenceMap<DebugLocalInfo> locals = getLocalVariables(0).locals;
     if (!locals.isEmpty()) {
       int firstLocalIndex = 0;
-      if (!method.isStatic()) {
+      if (!getMethod().isStatic()) {
         firstLocalIndex++;
       }
-      for (DexType value : method.method.proto.parameters.values) {
+      for (DexType value : getMethod().proto().parameters.values) {
         firstLocalIndex++;
         if (value.isLongType() || value.isDoubleType()) {
           firstLocalIndex++;
@@ -412,10 +417,6 @@
     inPrelude = false;
   }
 
-  private boolean isStatic() {
-    return method.accessFlags.isStatic();
-  }
-
   private boolean isCurrentlyGeneratingMethodSynchronization() {
     return currentlyGeneratingMethodSynchronization;
   }
@@ -427,9 +428,9 @@
   private void buildMethodEnterSynchronization(IRBuilder builder) {
     assert needsGeneratedMethodSynchronization;
     currentlyGeneratingMethodSynchronization = true;
-    DexType type = method.holder();
+    DexType type = method.getHolderType();
     int monitorRegister;
-    if (isStatic()) {
+    if (getMethod().isStatic()) {
       monitorRegister = state.push(type).register;
       state.pop();
       builder.addConstClass(monitorRegister, type);
@@ -633,7 +634,7 @@
       return ((CfNew) instruction).getType();
     }
     if (type.isUninitializedThis()) {
-      return method.holder();
+      return method.getHolderType();
     }
     assert type.isTop();
     return null;
@@ -777,7 +778,7 @@
                   .filter(insn -> insn instanceof CfPosition)
                   .map(insn -> ((CfPosition) insn).getPosition())
                   .collect(Collectors.toList()),
-          method.method);
+          method.getReference());
     }
     while (offset + 1 < code.getInstructions().size()) {
       CfInstruction insn = code.getInstructions().get(offset);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index f19ab4d..cd9bcd5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -36,10 +36,10 @@
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
 import com.android.tools.r8.graph.DexDebugEntry;
 import com.android.tools.r8.graph.DexDebugInfo;
-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.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.CanonicalPositions;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Position;
@@ -54,7 +54,7 @@
 public class DexSourceCode implements SourceCode {
 
   private final DexCode code;
-  private final DexEncodedMethod method;
+  private final ProgramMethod method;
 
   // Mapping from instruction offset to instruction index in the DexCode instruction array.
   private final Map<Integer, Integer> offsetToInstructionIndex = new HashMap<>();
@@ -75,7 +75,7 @@
   private final DexMethod originalMethod;
 
   public DexSourceCode(
-      DexCode code, DexEncodedMethod method, DexMethod originalMethod, Position callerPosition) {
+      DexCode code, ProgramMethod method, DexMethod originalMethod, Position callerPosition) {
     this.code = code;
     this.method = method;
     this.originalMethod = originalMethod;
@@ -139,7 +139,7 @@
     }
     builder.buildArgumentsWithRewrittenPrototypeChanges(
         code.registerSize - code.incomingRegisterSize,
-        method,
+        method.getDefinition(),
         DexSourceCode::doNothingWriteConsumer);
   }
 
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 35250d1..0dd98e2 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
@@ -23,7 +23,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 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.DexItem;
@@ -33,6 +32,7 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfo;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
@@ -392,8 +392,8 @@
   private int currentInstructionOffset = -1;
 
   final private ValueNumberGenerator valueNumberGenerator;
-  private final DexEncodedMethod method;
-  private DexEncodedMethod context;
+  private final ProgramMethod method;
+  private ProgramMethod context;
   public final AppView<?> appView;
   private final Origin origin;
   private final RewrittenPrototypeDescription prototypeChanges;
@@ -429,18 +429,18 @@
   private final IRMetadata metadata = new IRMetadata();
 
   public static IRBuilder create(
-      DexEncodedMethod method, AppView<?> appView, SourceCode source, Origin origin) {
+      ProgramMethod method, AppView<?> appView, SourceCode source, Origin origin) {
     return new IRBuilder(
         method,
         appView,
         source,
         origin,
-        lookupPrototypeChanges(appView, method.method),
+        lookupPrototypeChanges(appView, method),
         new ValueNumberGenerator());
   }
 
   public static IRBuilder createForInlining(
-      DexEncodedMethod method,
+      ProgramMethod method,
       AppView<?> appView,
       SourceCode source,
       Origin origin,
@@ -448,15 +448,15 @@
       ValueNumberGenerator valueNumberGenerator) {
     RewrittenPrototypeDescription protoChanges =
         processor.shouldApplyCodeRewritings(method)
-            ? lookupPrototypeChanges(appView, method.method)
+            ? lookupPrototypeChanges(appView, method)
             : RewrittenPrototypeDescription.none();
     return new IRBuilder(method, appView, source, origin, protoChanges, valueNumberGenerator);
   }
 
   private static RewrittenPrototypeDescription lookupPrototypeChanges(
-      AppView<?> appView, DexMethod method) {
+      AppView<?> appView, ProgramMethod method) {
     RewrittenPrototypeDescription prototypeChanges =
-        appView.graphLense().lookupPrototypeChanges(method);
+        appView.graphLense().lookupPrototypeChanges(method.getReference());
     if (Log.ENABLED && prototypeChanges.getArgumentInfoCollection().hasRemovedArguments()) {
       Log.info(
           IRBuilder.class,
@@ -469,7 +469,7 @@
   }
 
   private IRBuilder(
-      DexEncodedMethod method,
+      ProgramMethod method,
       AppView<?> appView,
       SourceCode source,
       Origin origin,
@@ -486,7 +486,7 @@
   }
 
   public DexEncodedMethod getMethod() {
-    return method;
+    return method.getDefinition();
   }
 
   public RewrittenPrototypeDescription getPrototypeChanges() {
@@ -494,7 +494,7 @@
   }
 
   public boolean isDebugMode() {
-    return appView.options().debug || method.getOptimizationInfo().isReachabilitySensitive();
+    return appView.options().debug || getMethod().getOptimizationInfo().isReachabilitySensitive();
   }
 
   public Int2ReferenceSortedMap<BlockInfo> getCFG() {
@@ -586,7 +586,7 @@
    * @param context Under what context this IRCode is built. Either the current method or caller.
    * @return The list of basic blocks. First block is the main entry.
    */
-  public IRCode build(DexEncodedMethod context) {
+  public IRCode build(ProgramMethod context) {
     assert source != null;
     source.setUp();
 
@@ -705,7 +705,7 @@
     // types, those could be still less precise at one single call site, where specific arguments
     // will be passed during (double) inlining. Instead of adding assumptions and removing invalid
     // ones, it's better not to insert assumptions for inlinee in the beginning.
-    CallSiteOptimizationInfo callSiteOptimizationInfo = method.getCallSiteOptimizationInfo();
+    CallSiteOptimizationInfo callSiteOptimizationInfo = getMethod().getCallSiteOptimizationInfo();
     if (method == context && appView.callSiteOptimizationInfoPropagator() != null) {
       appView.callSiteOptimizationInfoPropagator()
           .applyCallSiteOptimizationInfo(ir, callSiteOptimizationInfo);
@@ -724,7 +724,7 @@
   }
 
   public void constrainType(Value value, ValueTypeConstraint constraint) {
-    value.constrainType(constraint, method.method, origin, appView.options().reporter);
+    value.constrainType(constraint, method.getReference(), origin, appView.options().reporter);
   }
 
   private void addImpreciseInstruction(ImpreciseMemberTypeInstruction instruction) {
@@ -932,7 +932,8 @@
   void addThisArgument(int register) {
     boolean receiverCouldBeNull = context != null && context != method;
     Nullability nullability = receiverCouldBeNull ? maybeNull() : definitelyNotNull();
-    TypeElement receiverType = TypeElement.fromDexType(method.holder(), nullability, appView);
+    TypeElement receiverType =
+        TypeElement.fromDexType(method.getHolderType(), nullability, appView);
     addThisArgument(register, receiverType);
   }
 
@@ -1463,16 +1464,11 @@
       // therefore we use an invoke-direct instead. We need to do this as the Android Runtime
       // will not allow invoke-virtual of a private method.
       DexMethod invocationMethod = (DexMethod) item;
-      DexType holderType = method.holder();
-      if (invocationMethod.holder == holderType) {
-        DexClass holderClass = appView.definitionFor(holderType);
-        assert holderClass != null && holderClass.isProgramClass();
-        if (holderClass != null) {
-          DexEncodedMethod directTarget = holderClass.lookupDirectMethod(invocationMethod);
-          if (directTarget != null && !directTarget.isStatic()) {
-            assert invocationMethod.holder == directTarget.holder();
-            type = Type.DIRECT;
-          }
+      if (invocationMethod.holder == method.getHolderType()) {
+        DexEncodedMethod directTarget = method.getHolder().lookupDirectMethod(invocationMethod);
+        if (directTarget != null && !directTarget.isStatic()) {
+          assert invocationMethod.holder == directTarget.holder();
+          type = Type.DIRECT;
         }
       }
     }
@@ -1764,7 +1760,8 @@
   }
 
   public void addReturn(int value) {
-    if (method.method.proto.returnType == appView.dexItemFactory().voidType) {
+    DexType returnType = method.getDefinition().returnType();
+    if (returnType.isVoidType()) {
       assert prototypeChanges.hasBeenChangedToReturnVoid(appView);
       addReturn();
     } else {
@@ -1772,7 +1769,7 @@
           prototypeChanges.hasRewrittenReturnInfo()
               ? ValueTypeConstraint.fromDexType(
                   prototypeChanges.getRewrittenReturnInfo().getOldType())
-              : ValueTypeConstraint.fromDexType(method.method.proto.returnType);
+              : ValueTypeConstraint.fromDexType(returnType);
       Value in = readRegister(value, returnTypeConstraint);
       addReturn(new Return(in));
     }
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 7b15fcc..31fd3c0 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
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.IncludeAllResources;
 
@@ -16,7 +15,6 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexApplication.Builder;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -25,6 +23,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.TypeChecker;
 import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
 import com.android.tools.r8.ir.analysis.fieldaccess.FieldAccessAnalysis;
@@ -90,7 +89,6 @@
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.IdentifierNameStringMarker;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.LibraryMethodOverrideAnalysis;
@@ -105,11 +103,11 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.base.Suppliers;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -589,33 +587,34 @@
   private void convertMethods(DexProgramClass clazz) {
     boolean isReachabilitySensitive = clazz.hasReachabilitySensitiveAnnotation(options.itemFactory);
     // When converting all methods on a class always convert <clinit> first.
-    for (DexEncodedMethod method : clazz.directMethods()) {
-      if (method.isClassInitializer()) {
-        method.getMutableOptimizationInfo().setReachabilitySensitive(isReachabilitySensitive);
-        convertMethod(method);
-        break;
-      }
+    DexEncodedMethod classInitializer = clazz.getClassInitializer();
+    if (classInitializer != null) {
+      classInitializer
+          .getMutableOptimizationInfo()
+          .setReachabilitySensitive(isReachabilitySensitive);
+      convertMethod(new ProgramMethod(clazz, classInitializer));
     }
-    clazz.forEachMethod(
+    clazz.forEachProgramMethodMatching(
+        definition -> !definition.isClassInitializer(),
         method -> {
-          if (!method.isClassInitializer()) {
-            method.getMutableOptimizationInfo().setReachabilitySensitive(isReachabilitySensitive);
-            convertMethod(method);
-          }
+          DexEncodedMethod definition = method.getDefinition();
+          definition.getMutableOptimizationInfo().setReachabilitySensitive(isReachabilitySensitive);
+          convertMethod(method);
         });
   }
 
-  private void convertMethod(DexEncodedMethod method) {
-    if (method.getCode() != null) {
-      boolean matchesMethodFilter = options.methodMatchesFilter(method);
+  private void convertMethod(ProgramMethod method) {
+    DexEncodedMethod definition = method.getDefinition();
+    if (definition.getCode() != null) {
+      boolean matchesMethodFilter = options.methodMatchesFilter(definition);
       if (matchesMethodFilter) {
         if (appView.options().enableNeverMergePrefixes) {
           for (DexString neverMergePrefix : neverMergePrefixes) {
             // Synthetic classes will always be merged.
-            if (method.holder().isD8R8SynthesizedClassType()) {
+            if (method.getHolderType().isD8R8SynthesizedClassType()) {
               continue;
             }
-            if (method.holder().descriptor.startsWith(neverMergePrefix)) {
+            if (method.getHolderType().descriptor.startsWith(neverMergePrefix)) {
               seenNeverMergePrefix.getAndSet(true);
             } else {
               seenNotNeverMergePrefix.getAndSet(true);
@@ -639,17 +638,15 @@
           }
         }
         if (options.isGeneratingClassFiles()
-            || !(options.passthroughDexCode && method.getCode().isDexCode())) {
+            || !(options.passthroughDexCode && definition.getCode().isDexCode())) {
           // We do not process in call graph order, so anything could be a leaf.
           rewriteCode(
-              method,
-              simpleOptimizationFeedback,
-              OneTimeMethodProcessor.getInstance(ImmutableList.of(method)));
+              method, simpleOptimizationFeedback, OneTimeMethodProcessor.getInstance(method));
         } else {
-          assert method.getCode().isDexCode();
+          assert definition.getCode().isDexCode();
         }
         if (!options.isGeneratingClassFiles()) {
-          updateHighestSortingStrings(method);
+          updateHighestSortingStrings(definition);
         }
       }
     }
@@ -815,7 +812,7 @@
               outliner.applyOutliningCandidate(code);
               printMethod(code, "IR after outlining (SSA)", null);
               removeDeadCodeAndFinalizeIR(
-                  code.method(), code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
+                  code.context(), code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
             },
             executorService);
         feedback.updateVisibleOptimizationInfo();
@@ -863,11 +860,11 @@
     return builder.build();
   }
 
-  private void waveStart(Collection<DexEncodedMethod> wave) {
+  private void waveStart(ProgramMethodSet wave) {
     onWaveDoneActions = Collections.synchronizedList(new ArrayList<>());
   }
 
-  private void waveDone(Collection<DexEncodedMethod> wave) {
+  private void waveDone(ProgramMethodSet wave) {
     delayedOptimizationFeedback.refineAppInfoWithLiveness(appView.appInfo().withLiveness());
     delayedOptimizationFeedback.updateVisibleOptimizationInfo();
     if (options.enableFieldAssignmentTracker) {
@@ -904,13 +901,12 @@
       Consumer<IRCode> consumer, ExecutorService executorService)
       throws ExecutionException {
     assert !options.skipIR;
-    Set<DexEncodedMethod> methods = outliner.getMethodsSelectedForOutlining();
     ThreadUtils.processItems(
-        methods,
+        outliner.buildMethodsSelectedForOutlining(),
         method -> {
-          IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.holder()));
+          IRCode code = method.buildIR(appView);
           assert code != null;
-          assert !method.getCode().isOutlineCode();
+          assert !method.getDefinition().getCode().isOutlineCode();
           // Instead of repeating all the optimizations of rewriteCode(), only run the
           // optimizations needed for outlining: rewriteMoveResult() to remove out-values on
           // StringBuilder/StringBuffer method invocations, and removeDeadCode() to remove
@@ -924,15 +920,15 @@
   }
 
   private void processSynthesizedServiceLoaderMethods(
-      DexClass synthesizedClass, ExecutorService executorService) throws ExecutionException {
+      DexProgramClass synthesizedClass, ExecutorService executorService) throws ExecutionException {
     ThreadUtils.processItems(
-        synthesizedClass.methods(),
+        synthesizedClass::forEachProgramMethod,
         this::forEachSynthesizedServiceLoaderMethod,
         executorService);
   }
 
-  private void forEachSynthesizedServiceLoaderMethod(DexEncodedMethod method) {
-    IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.holder()));
+  private void forEachSynthesizedServiceLoaderMethod(ProgramMethod method) {
+    IRCode code = method.buildIR(appView);
     assert code != null;
     codeRewriter.rewriteMoveResult(code);
     removeDeadCodeAndFinalizeIR(
@@ -1017,8 +1013,8 @@
   public void optimizeSynthesizedClass(
       DexProgramClass clazz, ExecutorService executorService)
       throws ExecutionException {
-    Set<DexEncodedMethod> methods = Sets.newIdentityHashSet();
-    clazz.forEachMethod(methods::add);
+    ProgramMethodSet methods = ProgramMethodSet.create();
+    clazz.forEachProgramMethod(methods::add);
     // Process the generated class, but don't apply any outlining.
     processMethodsConcurrently(methods, executorService);
   }
@@ -1026,15 +1022,15 @@
   public void optimizeSynthesizedClasses(
       Collection<DexProgramClass> classes, ExecutorService executorService)
       throws ExecutionException {
-    Set<DexEncodedMethod> methods = Sets.newIdentityHashSet();
+    ProgramMethodSet methods = ProgramMethodSet.create();
     for (DexProgramClass clazz : classes) {
-      clazz.forEachMethod(methods::add);
+      clazz.forEachProgramMethod(methods::add);
     }
     processMethodsConcurrently(methods, executorService);
   }
 
-  public void optimizeSynthesizedMethod(DexEncodedMethod method) {
-    if (!method.isProcessed()) {
+  public void optimizeSynthesizedMethod(ProgramMethod method) {
+    if (!method.getDefinition().isProcessed()) {
       // Process the generated method, but don't apply any outlining.
       processMethod(
           method,
@@ -1043,12 +1039,13 @@
     }
   }
 
-  public void processMethodsConcurrently(
-      Collection<DexEncodedMethod> methods, ExecutorService executorService)
+  public void processMethodsConcurrently(ProgramMethodSet methods, ExecutorService executorService)
       throws ExecutionException {
-    OneTimeMethodProcessor processor = OneTimeMethodProcessor.getInstance(methods);
-    processor.forEachWave(
-        method -> processMethod(method, delayedOptimizationFeedback, processor), executorService);
+    if (!methods.isEmpty()) {
+      OneTimeMethodProcessor processor = OneTimeMethodProcessor.getInstance(methods);
+      processor.forEachWave(
+          method -> processMethod(method, delayedOptimizationFeedback, processor), executorService);
+    }
   }
 
   private String logCode(InternalOptions options, DexEncodedMethod method) {
@@ -1067,14 +1064,15 @@
 
   // TODO(b/140766440): Make this receive a list of CodeOptimizations to conduct.
   public Timing processMethod(
-      DexEncodedMethod method, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
-    Code code = method.getCode();
-    boolean matchesMethodFilter = options.methodMatchesFilter(method);
+      ProgramMethod method, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
+    DexEncodedMethod definition = method.getDefinition();
+    Code code = definition.getCode();
+    boolean matchesMethodFilter = options.methodMatchesFilter(definition);
     if (code != null && matchesMethodFilter) {
       return rewriteCode(method, feedback, methodProcessor);
     } else {
       // Mark abstract methods as processed as well.
-      method.markProcessed(ConstraintWithTarget.NEVER);
+      definition.markProcessed(ConstraintWithTarget.NEVER);
     }
     return Timing.empty();
   }
@@ -1088,35 +1086,33 @@
   }
 
   private Timing rewriteCode(
-      DexEncodedMethod method, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
-    Origin origin = appView.appInfo().originFor(method.holder());
+      ProgramMethod method, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
     return ExceptionUtils.withOriginAttachmentHandler(
-        origin,
-        new MethodPosition(method.method),
-        () -> rewriteCodeInternal(method, feedback, methodProcessor, origin));
+        method.getOrigin(),
+        new MethodPosition(method.getReference()),
+        () -> rewriteCodeInternal(method, feedback, methodProcessor));
   }
 
   private Timing rewriteCodeInternal(
-      DexEncodedMethod method,
-      OptimizationFeedback feedback,
-      MethodProcessor methodProcessor,
-      Origin origin) {
-
+      ProgramMethod method, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
     if (options.verbose) {
       options.reporter.info(
           new StringDiagnostic("Processing: " + method.toSourceString()));
     }
     if (Log.ENABLED) {
-      Log.debug(getClass(), "Original code for %s:\n%s",
-          method.toSourceString(), logCode(options, method));
+      Log.debug(
+          getClass(),
+          "Original code for %s:\n%s",
+          method.toSourceString(),
+          logCode(options, method.getDefinition()));
     }
     if (options.skipIR) {
-      feedback.markProcessed(method, ConstraintWithTarget.NEVER);
+      feedback.markProcessed(method.getDefinition(), ConstraintWithTarget.NEVER);
       return Timing.empty();
     }
-    IRCode code = method.buildIR(appView, origin);
+    IRCode code = method.buildIR(appView);
     if (code == null) {
-      feedback.markProcessed(method, ConstraintWithTarget.NEVER);
+      feedback.markProcessed(method.getDefinition(), ConstraintWithTarget.NEVER);
       return Timing.empty();
     }
     return optimize(code, feedback, methodProcessor);
@@ -1125,8 +1121,9 @@
   // TODO(b/140766440): Convert all sub steps an implementer of CodeOptimization
   private Timing optimize(
       IRCode code, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
-    DexEncodedMethod method = code.method();
-    DexProgramClass holder = asProgramClassOrNull(appView.definitionForHolder(method));
+    ProgramMethod context = code.context();
+    DexEncodedMethod method = context.getDefinition();
+    DexProgramClass holder = context.getHolder();
     assert holder != null;
 
     Timing timing = Timing.create(method.qualifiedName(), options);
@@ -1157,7 +1154,7 @@
     if (appView.graphLense().hasCodeRewritings()) {
       assert lensCodeRewriter != null;
       timing.begin("Lens rewrite");
-      lensCodeRewriter.rewrite(code, method);
+      lensCodeRewriter.rewrite(code, context);
       timing.end();
     }
 
@@ -1175,7 +1172,7 @@
 
     if (lambdaMerger != null) {
       timing.begin("Merge lambdas");
-      lambdaMerger.rewriteCode(method, code, inliner, methodProcessor);
+      lambdaMerger.rewriteCode(code.context(), code, inliner, methodProcessor);
       timing.end();
       assert code.isConsistentSSA();
     }
@@ -1211,7 +1208,7 @@
 
     if (identifierNameStringMarker != null) {
       timing.begin("Decouple identifier-name strings");
-      identifierNameStringMarker.decoupleIdentifierNameStringsInMethod(method, code);
+      identifierNameStringMarker.decoupleIdentifierNameStringsInMethod(code);
       timing.end();
       assert code.isConsistentSSA();
     }
@@ -1242,14 +1239,14 @@
 
     previous = printMethod(code, "IR after generated extension registry shrinking (SSA)", previous);
 
-    appView.withGeneratedMessageLiteShrinker(shrinker -> shrinker.run(method, code));
+    appView.withGeneratedMessageLiteShrinker(shrinker -> shrinker.run(code));
     timing.end();
 
     previous = printMethod(code, "IR after generated message lite shrinking (SSA)", previous);
 
     if (!isDebugMode && options.enableInlining && inliner != null) {
       timing.begin("Inlining");
-      inliner.performInlining(method, code, feedback, methodProcessor);
+      inliner.performInlining(code.context(), code, feedback, methodProcessor);
       timing.end();
       assert code.verifyTypes(appView);
     }
@@ -1428,7 +1425,7 @@
           codeRewriter,
           stringOptimizer,
           enumValueOptimizer,
-          method,
+          code.context(),
           code,
           feedback,
           methodProcessor,
@@ -1436,8 +1433,7 @@
           Suppliers.memoize(
               () ->
                   inliner.createDefaultOracle(
-                      method,
-                      code,
+                      code.context(),
                       methodProcessor,
                       options.classInliningInstructionLimit,
                       // Inlining instruction allowance is not needed for the class inliner since it
@@ -1452,7 +1448,7 @@
 
     if (d8NestBasedAccessDesugaring != null) {
       timing.begin("Desugar nest access");
-      d8NestBasedAccessDesugaring.rewriteNestBasedAccesses(method, code, appView);
+      d8NestBasedAccessDesugaring.rewriteNestBasedAccesses(context, code, appView);
       timing.end();
       assert code.isConsistentSSA();
     }
@@ -1491,7 +1487,7 @@
 
     if (lambdaMerger != null) {
       timing.begin("Analyze lambda merging");
-      lambdaMerger.analyzeCode(method, code);
+      lambdaMerger.analyzeCode(code.context(), code);
       timing.end();
       assert code.isConsistentSSA();
     }
@@ -1515,7 +1511,7 @@
       // Remove string switches prior to canonicalization to ensure that the constants that are
       // being introduced will be canonicalized if possible.
       timing.begin("Remove string switch");
-      stringSwitchRemover.run(method, code);
+      stringSwitchRemover.run(code);
       timing.end();
     }
 
@@ -1549,7 +1545,7 @@
 
     if (classStaticizer != null) {
       timing.begin("Identify staticizing candidates");
-      classStaticizer.examineMethodCode(method, code);
+      classStaticizer.examineMethodCode(code);
       timing.end();
     }
 
@@ -1591,7 +1587,7 @@
 
     printMethod(code, "Optimized IR (SSA)", previous);
     timing.begin("Finalize IR");
-    finalizeIR(method, code, feedback, timing);
+    finalizeIR(code, feedback, timing);
     timing.end();
     return timing;
   }
@@ -1653,22 +1649,21 @@
   }
 
   public void removeDeadCodeAndFinalizeIR(
-      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+      ProgramMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
     if (stringSwitchRemover != null) {
-      stringSwitchRemover.run(method, code);
+      stringSwitchRemover.run(code);
     }
     deadCodeRemover.run(code, timing);
-    finalizeIR(method, code, feedback, timing);
+    finalizeIR(code, feedback, timing);
   }
 
-  public void finalizeIR(
-      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+  public void finalizeIR(IRCode code, OptimizationFeedback feedback, Timing timing) {
     code.traceBlocks();
     if (options.isGeneratingClassFiles()) {
-      finalizeToCf(method, code, feedback);
+      finalizeToCf(code, feedback);
     } else {
       assert options.isGeneratingDex();
-      finalizeToDex(method, code, feedback, timing);
+      finalizeToDex(code, feedback, timing);
     }
   }
 
@@ -1682,16 +1677,17 @@
     feedback.markProcessed(method, ConstraintWithTarget.ALWAYS);
   }
 
-  private void finalizeToCf(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+  private void finalizeToCf(IRCode code, OptimizationFeedback feedback) {
+    DexEncodedMethod method = code.method();
     assert !method.getCode().isDexCode();
     CfBuilder builder = new CfBuilder(appView, method, code);
     CfCode result = builder.build(deadCodeRemover);
     method.setCode(result, appView);
-    markProcessed(method, code, feedback);
+    markProcessed(code, feedback);
   }
 
-  private void finalizeToDex(
-      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+  private void finalizeToDex(IRCode code, OptimizationFeedback feedback, Timing timing) {
+    DexEncodedMethod method = code.method();
     // Workaround massive dex2oat memory use for self-recursive methods.
     CodeRewriter.disableDex2OatInliningForSelfRecursiveMethods(appView, code);
     // Perform register allocation.
@@ -1706,28 +1702,31 @@
     }
     printMethod(code, "Final IR (non-SSA)", null);
     timing.begin("Marking processed");
-    markProcessed(method, code, feedback);
+    markProcessed(code, feedback);
     timing.end();
   }
 
-  private void markProcessed(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+  private void markProcessed(IRCode code, OptimizationFeedback feedback) {
     // After all the optimizations have take place, we compute whether method should be inlined.
+    ProgramMethod method = code.context();
     ConstraintWithTarget state =
         shouldComputeInliningConstraint(method)
             ? inliner.computeInliningConstraint(code, method)
             : ConstraintWithTarget.NEVER;
-    feedback.markProcessed(method, state);
+    feedback.markProcessed(method.getDefinition(), state);
   }
 
-  private boolean shouldComputeInliningConstraint(DexEncodedMethod method) {
+  private boolean shouldComputeInliningConstraint(ProgramMethod method) {
     if (!options.enableInlining || inliner == null) {
       return false;
     }
-    if (method.isClassInitializer() || method.getOptimizationInfo().isReachabilitySensitive()) {
+    DexEncodedMethod definition = method.getDefinition();
+    if (definition.isClassInitializer()
+        || definition.getOptimizationInfo().isReachabilitySensitive()) {
       return false;
     }
     if (appView.appInfo().hasLiveness()
-        && appView.appInfo().withLiveness().isPinned(method.method)) {
+        && appView.appInfo().withLiveness().isPinned(method.getReference())) {
       return false;
     }
     return true;
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 e6f5e43..e94d07e 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
@@ -35,7 +35,6 @@
 import com.android.tools.r8.graph.AppView;
 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;
@@ -49,6 +48,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfo;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
@@ -121,7 +121,7 @@
   }
 
   /** Replace type appearances, invoke targets and field accesses with actual definitions. */
-  public void rewrite(IRCode code, DexEncodedMethod method) {
+  public void rewrite(IRCode code, ProgramMethod method) {
     Set<Phi> affectedPhis =
         enumUnboxer != null ? enumUnboxer.rewriteCode(code) : Sets.newIdentityHashSet();
     GraphLense graphLense = appView.graphLense();
@@ -214,10 +214,10 @@
                 continue;
               }
               if (invoke.isInvokeDirect()) {
-                checkInvokeDirect(method.method, invoke.asInvokeDirect());
+                checkInvokeDirect(method.getReference(), invoke.asInvokeDirect());
               }
               GraphLenseLookupResult lenseLookup =
-                  graphLense.lookupMethod(invokedMethod, method.method, invoke.getType());
+                  graphLense.lookupMethod(invokedMethod, method.getReference(), invoke.getType());
               DexMethod actualTarget = lenseLookup.getMethod();
               Invoke.Type actualInvokeType = lenseLookup.getType();
               if (actualTarget != invokedMethod || invoke.getType() != actualInvokeType) {
@@ -334,7 +334,7 @@
               DexField field = instanceGet.getField();
               DexField actualField = graphLense.lookupField(field);
               DexMethod replacementMethod =
-                  graphLense.lookupGetFieldForMethod(actualField, method.method);
+                  graphLense.lookupGetFieldForMethod(actualField, method.getReference());
               if (replacementMethod != null) {
                 Value newOutValue = makeOutValue(current, code);
                 iterator.replaceCurrentInstruction(
@@ -359,7 +359,7 @@
               DexField field = instancePut.getField();
               DexField actualField = graphLense.lookupField(field);
               DexMethod replacementMethod =
-                  graphLense.lookupPutFieldForMethod(actualField, method.method);
+                  graphLense.lookupPutFieldForMethod(actualField, method.getReference());
               if (replacementMethod != null) {
                 iterator.replaceCurrentInstruction(
                     new InvokeStatic(replacementMethod, null, current.inValues()));
@@ -381,7 +381,7 @@
               DexField field = staticGet.getField();
               DexField actualField = graphLense.lookupField(field);
               DexMethod replacementMethod =
-                  graphLense.lookupGetFieldForMethod(actualField, method.method);
+                  graphLense.lookupGetFieldForMethod(actualField, method.getReference());
               if (replacementMethod != null) {
                 Value newOutValue = makeOutValue(current, code);
                 iterator.replaceCurrentInstruction(
@@ -405,7 +405,7 @@
               DexField field = staticPut.getField();
               DexField actualField = graphLense.lookupField(field);
               DexMethod replacementMethod =
-                  graphLense.lookupPutFieldForMethod(actualField, method.method);
+                  graphLense.lookupPutFieldForMethod(actualField, method.getReference());
               if (replacementMethod != null) {
                 iterator.replaceCurrentInstruction(
                     new InvokeStatic(replacementMethod, current.outValue(), current.inValues()));
@@ -582,7 +582,7 @@
     return TypeElement.getNull();
   }
 
-  public DexCallSite rewriteCallSite(DexCallSite callSite, DexEncodedMethod context) {
+  public DexCallSite rewriteCallSite(DexCallSite callSite, ProgramMethod context) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     DexProto newMethodProto =
         dexItemFactory.applyClassMappingToProto(
@@ -682,7 +682,7 @@
   }
 
   private List<DexValue> rewriteBootstrapArgs(
-      List<DexValue> bootstrapArgs, DexEncodedMethod method, MethodHandleUse use) {
+      List<DexValue> bootstrapArgs, ProgramMethod method, MethodHandleUse use) {
     List<DexValue> newBootstrapArgs = null;
     boolean changed = false;
     for (int i = 0; i < bootstrapArgs.size(); i++) {
@@ -719,19 +719,21 @@
   }
 
   private DexValueMethodHandle rewriteDexValueMethodHandle(
-      DexValueMethodHandle methodHandle, DexEncodedMethod context, MethodHandleUse use) {
+      DexValueMethodHandle methodHandle, ProgramMethod context, MethodHandleUse use) {
     DexMethodHandle oldHandle = methodHandle.value;
     DexMethodHandle newHandle = rewriteDexMethodHandle(oldHandle, context, use);
     return newHandle != oldHandle ? new DexValueMethodHandle(newHandle) : methodHandle;
   }
 
   private DexMethodHandle rewriteDexMethodHandle(
-      DexMethodHandle methodHandle, DexEncodedMethod context, MethodHandleUse use) {
+      DexMethodHandle methodHandle, ProgramMethod context, MethodHandleUse use) {
     if (methodHandle.isMethodHandle()) {
       DexMethod invokedMethod = methodHandle.asMethod();
       MethodHandleType oldType = methodHandle.type;
       GraphLenseLookupResult lenseLookup =
-          appView.graphLense().lookupMethod(invokedMethod, context.method, oldType.toInvokeType());
+          appView
+              .graphLense()
+              .lookupMethod(invokedMethod, context.getReference(), oldType.toInvokeType());
       DexMethod rewrittenTarget = lenseLookup.getMethod();
       DexMethod actualTarget;
       MethodHandleType newType;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index 0c2eb60..b7ee7e1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 
 public interface MethodProcessor {
 
@@ -15,7 +15,7 @@
 
   Phase getPhase();
 
-  boolean shouldApplyCodeRewritings(DexEncodedMethod method);
+  boolean shouldApplyCodeRewritings(ProgramMethod method);
 
   default boolean isPrimary() {
     return getPhase() == Phase.PRIMARY;
@@ -29,5 +29,5 @@
     return CallSiteInformation.empty();
   }
 
-  boolean isProcessedConcurrently(DexEncodedMethod method);
+  boolean isProcessedConcurrently(ProgramMethod method);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
index 939395f..071bfcb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
@@ -3,10 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
-import java.util.Collection;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
@@ -16,9 +16,9 @@
  */
 public class OneTimeMethodProcessor implements MethodProcessor {
 
-  private Collection<DexEncodedMethod> wave;
+  private ProgramMethodSet wave;
 
-  private OneTimeMethodProcessor(Collection<DexEncodedMethod> methodsToProcess) {
+  private OneTimeMethodProcessor(ProgramMethodSet methodsToProcess) {
     this.wave = methodsToProcess;
   }
 
@@ -26,12 +26,16 @@
     return new OneTimeMethodProcessor(null);
   }
 
-  public static OneTimeMethodProcessor getInstance(Collection<DexEncodedMethod> methodsToProcess) {
+  public static OneTimeMethodProcessor getInstance(ProgramMethod methodToProcess) {
+    return new OneTimeMethodProcessor(ProgramMethodSet.create(methodToProcess));
+  }
+
+  public static OneTimeMethodProcessor getInstance(ProgramMethodSet methodsToProcess) {
     return new OneTimeMethodProcessor(methodsToProcess);
   }
 
   @Override
-  public boolean shouldApplyCodeRewritings(DexEncodedMethod method) {
+  public boolean shouldApplyCodeRewritings(ProgramMethod method) {
     return true;
   }
 
@@ -41,12 +45,12 @@
   }
 
   @Override
-  public boolean isProcessedConcurrently(DexEncodedMethod method) {
+  public boolean isProcessedConcurrently(ProgramMethod method) {
     return wave != null && wave.contains(method);
   }
 
   public <E extends Exception> void forEachWave(
-      ThrowingConsumer<DexEncodedMethod, E> consumer, ExecutorService executorService)
+      ThrowingConsumer<ProgramMethod, E> consumer, ExecutorService executorService)
       throws ExecutionException {
     ThreadUtils.processItems(wave, consumer, executorService);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PartialCallGraphBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/PartialCallGraphBuilder.java
index bfaf62c..e094867 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PartialCallGraphBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PartialCallGraphBuilder.java
@@ -4,17 +4,18 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ThreadUtils;
-import java.util.Set;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
 public class PartialCallGraphBuilder extends CallGraphBuilderBase {
-  private final Set<DexEncodedMethod> seeds;
 
-  PartialCallGraphBuilder(AppView<AppInfoWithLiveness> appView, Set<DexEncodedMethod> seeds) {
+  private final ProgramMethodSet seeds;
+
+  PartialCallGraphBuilder(AppView<AppInfoWithLiveness> appView, ProgramMethodSet seeds) {
     super(appView);
     assert seeds != null && !seeds.isEmpty();
     this.seeds = seeds;
@@ -25,17 +26,14 @@
     ThreadUtils.processItems(seeds, this::processMethod, executorService);
   }
 
-  private void processMethod(DexEncodedMethod method) {
-    if (method.hasCode()) {
-      method.registerCodeReferences(
-          new InvokeExtractor(getOrCreateNode(method), seeds::contains));
-    }
+  private void processMethod(ProgramMethod method) {
+    method.registerCodeReferences(new InvokeExtractor(getOrCreateNode(method), seeds::contains));
   }
 
   @Override
   boolean verifyAllMethodsWithCodeExists() {
-    for (DexEncodedMethod method : seeds) {
-      assert !method.hasCode() || nodes.get(method.method) != null;
+    for (ProgramMethod method : seeds) {
+      assert method.getDefinition().hasCode() == (nodes.get(method.getReference()) != null);
     }
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 328365e..7c136f6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -6,36 +6,34 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.IROrdering;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
+import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Deque;
 import java.util.IdentityHashMap;
 import java.util.LinkedHashSet;
 import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.stream.Collectors;
 
 public class PostMethodProcessor implements MethodProcessor {
 
   private final AppView<AppInfoWithLiveness> appView;
   private final Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap;
-  private final Deque<Set<DexEncodedMethod>> waves;
-  private Set<DexEncodedMethod> wave;
-  private final Set<DexEncodedMethod> processed = Sets.newIdentityHashSet();
+  private final Deque<ProgramMethodSet> waves;
+  private ProgramMethodSet wave;
+  private final ProgramMethodSet processed = ProgramMethodSet.create();
 
   private PostMethodProcessor(
       AppView<AppInfoWithLiveness> appView,
@@ -52,7 +50,7 @@
   }
 
   @Override
-  public boolean shouldApplyCodeRewritings(DexEncodedMethod method) {
+  public boolean shouldApplyCodeRewritings(ProgramMethod method) {
     assert !wave.contains(method);
     return !processed.contains(method);
   }
@@ -60,30 +58,33 @@
   public static class Builder {
 
     private final Collection<CodeOptimization> defaultCodeOptimizations;
-    private final Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap =
-        Maps.newIdentityHashMap();
+    private final LongLivedProgramMethodSetBuilder methodsMap =
+        new LongLivedProgramMethodSetBuilder();
+    private final Map<DexEncodedMethod, Collection<CodeOptimization>> optimizationsMap =
+        new IdentityHashMap<>();
 
     Builder(Collection<CodeOptimization> defaultCodeOptimizations) {
       this.defaultCodeOptimizations = defaultCodeOptimizations;
     }
 
     private void put(
-        Set<DexEncodedMethod> methodsToRevisit, Collection<CodeOptimization> codeOptimizations) {
+        ProgramMethodSet methodsToRevisit, Collection<CodeOptimization> codeOptimizations) {
       if (codeOptimizations.isEmpty()) {
         // Nothing to conduct.
         return;
       }
-      for (DexEncodedMethod method : methodsToRevisit) {
-        methodsMap
+      for (ProgramMethod method : methodsToRevisit) {
+        methodsMap.add(method);
+        optimizationsMap
             .computeIfAbsent(
-                method,
+                method.getDefinition(),
                 // Optimization order might matter, hence a collection that preserves orderings.
                 k -> new LinkedHashSet<>())
             .addAll(codeOptimizations);
       }
     }
 
-    public void put(Set<DexEncodedMethod> methodsToRevisit) {
+    public void put(ProgramMethodSet methodsToRevisit) {
       put(methodsToRevisit, defaultCodeOptimizations);
     }
 
@@ -100,45 +101,54 @@
     // new signature. The compiler needs to update the set of methods that must be reprocessed
     // according to the graph lens.
     public void mapDexEncodedMethods(AppView<?> appView) {
-      Map<DexEncodedMethod, Collection<CodeOptimization>> newMethodsMap = new IdentityHashMap<>();
-      methodsMap.forEach(
-          (dexEncodedMethod, optimizations) -> {
-            newMethodsMap.put(
-                appView.graphLense().mapDexEncodedMethod(dexEncodedMethod, appView), optimizations);
-          });
-      methodsMap.clear();
-      methodsMap.putAll(newMethodsMap);
+      Map<DexEncodedMethod, Collection<CodeOptimization>> newOptimizationsMap =
+          new IdentityHashMap<>();
+      optimizationsMap.forEach(
+          (method, optimizations) ->
+              newOptimizationsMap.put(
+                  appView.graphLense().mapDexEncodedMethod(method, appView), optimizations));
+      optimizationsMap.clear();
+      optimizationsMap.putAll(newOptimizationsMap);
     }
 
     PostMethodProcessor build(
         AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
         throws ExecutionException {
       if (!appView.appInfo().reprocess.isEmpty()) {
-        put(
-            appView.appInfo().reprocess.stream()
-                .map(appView::definitionFor)
-                .filter(Objects::nonNull)
-                .collect(Collectors.toSet()));
+        ProgramMethodSet set = ProgramMethodSet.create();
+        appView
+            .appInfo()
+            .reprocess
+            .forEach(
+                reference -> {
+                  DexEncodedMethod definition = appView.definitionFor(reference);
+                  if (definition != null) {
+                    DexProgramClass clazz =
+                        appView.definitionForHolder(definition).asProgramClass();
+                    set.createAndAdd(clazz, definition);
+                  }
+                });
+        put(set);
       }
-      if (methodsMap.keySet().isEmpty()) {
+      if (methodsMap.isEmpty()) {
         // Nothing to revisit.
         return null;
       }
       CallGraph callGraph =
-          new PartialCallGraphBuilder(appView, methodsMap.keySet())
+          new PartialCallGraphBuilder(appView, methodsMap.build(appView))
               .build(executorService, timing);
-      return new PostMethodProcessor(appView, methodsMap, callGraph);
+      return new PostMethodProcessor(appView, optimizationsMap, callGraph);
     }
   }
 
-  private Deque<Set<DexEncodedMethod>> createWaves(AppView<?> appView, CallGraph callGraph) {
+  private Deque<ProgramMethodSet> createWaves(AppView<?> appView, CallGraph callGraph) {
     IROrdering shuffle = appView.options().testing.irOrdering;
-    Deque<Set<DexEncodedMethod>> waves = new ArrayDeque<>();
+    Deque<ProgramMethodSet> waves = new ArrayDeque<>();
 
     int waveCount = 1;
     while (!callGraph.isEmpty()) {
-      Set<DexEncodedMethod> wave = callGraph.extractRoots();
-      waves.addLast(shuffle.order(wave));
+      ProgramMethodSet wave = callGraph.extractRoots();
+      waves.addLast(wave);
       if (Log.ENABLED && Log.isLoggingEnabledFor(PostMethodProcessor.class)) {
         Log.info(getClass(), "Wave #%d: %d", waveCount++, wave.size());
       }
@@ -148,7 +158,7 @@
   }
 
   @Override
-  public boolean isProcessedConcurrently(DexEncodedMethod method) {
+  public boolean isProcessedConcurrently(ProgramMethod method) {
     return wave != null && wave.contains(method);
   }
 
@@ -160,7 +170,7 @@
       ThreadUtils.processItems(
           wave,
           method -> {
-            Collection<CodeOptimization> codeOptimizations = methodsMap.get(method);
+            Collection<CodeOptimization> codeOptimizations = methodsMap.get(method.getDefinition());
             assert codeOptimizations != null && !codeOptimizations.isEmpty();
             forEachMethod(method, codeOptimizations, feedback);
           },
@@ -170,19 +180,18 @@
   }
 
   private void forEachMethod(
-      DexEncodedMethod method,
+      ProgramMethod method,
       Collection<CodeOptimization> codeOptimizations,
       OptimizationFeedback feedback) {
     // TODO(b/140766440): Make IRConverter#process receive a list of CodeOptimization to conduct.
     //   Then, we can share IRCode creation there.
-    Origin origin = appView.appInfo().originFor(method.holder());
     if (appView.options().skipIR) {
-      feedback.markProcessed(method, ConstraintWithTarget.NEVER);
+      feedback.markProcessed(method.getDefinition(), ConstraintWithTarget.NEVER);
       return;
     }
-    IRCode code = method.buildIR(appView, origin);
+    IRCode code = method.buildIR(appView);
     if (code == null) {
-      feedback.markProcessed(method, ConstraintWithTarget.NEVER);
+      feedback.markProcessed(method.getDefinition(), ConstraintWithTarget.NEVER);
       return;
     }
     // TODO(b/140768815): Reprocessing may trigger more methods to revisit. Update waves on-the-fly.
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java b/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java
index 5ab18b3..7f06207 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostOptimization.java
@@ -3,19 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.Collection;
-import java.util.Set;
 
 /**
  * An abstraction of optimizations that require post processing of methods.
  */
 public interface PostOptimization {
 
-  /**
-   * @return a set of methods that need post processing.
-   */
-  Set<DexEncodedMethod> methodsToRevisit();
+  /** @return a set of methods that need post processing. */
+  ProgramMethodSet methodsToRevisit();
 
   // TODO(b/127694949): different CodeOptimization for primary processor v.s. post processor?
   //  In that way, instead of internal state changes, such as COLLECT v.s. APPLY or REVISIT,
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index 5b26255..2a138cb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -5,19 +5,17 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.IROrdering;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.ThrowingFunction;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.Timing.TimingMerger;
-import com.google.common.collect.Sets;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.ArrayDeque;
-import java.util.Collection;
 import java.util.Deque;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -32,13 +30,13 @@
 
   interface WaveStartAction {
 
-    void notifyWaveStart(Collection<DexEncodedMethod> wave);
+    void notifyWaveStart(ProgramMethodSet wave);
   }
 
   private final CallSiteInformation callSiteInformation;
   private final PostMethodProcessor.Builder postMethodProcessorBuilder;
-  private final Deque<Collection<DexEncodedMethod>> waves;
-  private Collection<DexEncodedMethod> wave;
+  private final Deque<ProgramMethodSet> waves;
+  private ProgramMethodSet wave;
 
   private PrimaryMethodProcessor(
       AppView<AppInfoWithLiveness> appView,
@@ -65,9 +63,9 @@
   }
 
   @Override
-  public boolean shouldApplyCodeRewritings(DexEncodedMethod method) {
+  public boolean shouldApplyCodeRewritings(ProgramMethod method) {
     assert !wave.contains(method);
-    return !method.isProcessed();
+    return !method.getDefinition().isProcessed();
   }
 
   @Override
@@ -75,23 +73,22 @@
     return callSiteInformation;
   }
 
-  private Deque<Collection<DexEncodedMethod>> createWaves(
+  private Deque<ProgramMethodSet> createWaves(
       AppView<?> appView, CallGraph callGraph, CallSiteInformation callSiteInformation) {
     InternalOptions options = appView.options();
-    IROrdering shuffle = options.testing.irOrdering;
-    Deque<Collection<DexEncodedMethod>> waves = new ArrayDeque<>();
-
+    Deque<ProgramMethodSet> waves = new ArrayDeque<>();
     Set<Node> nodes = callGraph.nodes;
-    Set<DexEncodedMethod> reprocessing = Sets.newIdentityHashSet();
+    ProgramMethodSet reprocessing = ProgramMethodSet.create();
     int waveCount = 1;
     while (!nodes.isEmpty()) {
-      Set<DexEncodedMethod> wave = callGraph.extractLeaves();
-      for (DexEncodedMethod method : wave) {
-        if (callSiteInformation.hasSingleCallSite(method.method)) {
-          callGraph.cycleEliminationResult.forEachRemovedCaller(method, reprocessing::add);
-        }
-      }
-      waves.addLast(shuffle.order(wave));
+      ProgramMethodSet wave = callGraph.extractLeaves();
+      wave.forEach(
+          method -> {
+            if (callSiteInformation.hasSingleCallSite(method)) {
+              callGraph.cycleEliminationResult.forEachRemovedCaller(method, reprocessing::add);
+            }
+          });
+      waves.addLast(wave);
       if (Log.ENABLED && Log.isLoggingEnabledFor(PrimaryMethodProcessor.class)) {
         Log.info(getClass(), "Wave #%d: %d", waveCount++, wave.size());
       }
@@ -104,7 +101,7 @@
   }
 
   @Override
-  public boolean isProcessedConcurrently(DexEncodedMethod method) {
+  public boolean isProcessedConcurrently(ProgramMethod method) {
     return wave != null && wave.contains(method);
   }
 
@@ -115,9 +112,9 @@
    * processed at the same time is passed. This can be used to avoid races in concurrent processing.
    */
   <E extends Exception> void forEachMethod(
-      ThrowingFunction<DexEncodedMethod, Timing, E> consumer,
+      ThrowingFunction<ProgramMethod, Timing, E> consumer,
       WaveStartAction waveStartAction,
-      Consumer<Collection<DexEncodedMethod>> waveDone,
+      Consumer<ProgramMethodSet> waveDone,
       Timing timing,
       ExecutorService executorService)
       throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
index ebce25c..52a7d0a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
@@ -9,7 +9,6 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
@@ -60,7 +59,7 @@
     this.throwingInfo = ThrowingInfo.defaultForConstString(appView.options());
   }
 
-  void run(DexEncodedMethod method, IRCode code) {
+  void run(IRCode code) {
     if (!code.metadata().mayHaveStringSwitch()) {
       assert Streams.stream(code.instructions()).noneMatch(Instruction::isStringSwitch);
       return;
@@ -98,8 +97,7 @@
     }
 
     if (identifierNameStringMarker != null) {
-      identifierNameStringMarker.decoupleIdentifierNameStringsInBlocks(
-          method, code, newBlocksWithStrings);
+      identifierNameStringMarker.decoupleIdentifierNameStringsInBlocks(code, newBlocksWithStrings);
     }
 
     assert code.isConsistentSSA();
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 56c1ff5..47ee95f 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
@@ -18,11 +18,13 @@
 import com.android.tools.r8.graph.DexType;
 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.graph.ResolutionResult;
 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.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableList.Builder;
@@ -172,7 +174,7 @@
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final DexItemFactory dexItemFactory;
   private final InterfaceMethodRewriter rewriter;
-  private final Consumer<DexEncodedMethod> newSynthesizedMethodConsumer;
+  private final Consumer<ProgramMethod> newSynthesizedMethodConsumer;
   private final MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
   private final boolean needsLibraryInfo;
 
@@ -186,13 +188,13 @@
   private final Map<DexClass, MethodSignatures> interfaceInfo = new IdentityHashMap<>();
 
   // Mapping from actual program classes to the synthesized forwarding methods to be created.
-  private final Map<DexProgramClass, List<DexEncodedMethod>> newSyntheticMethods =
+  private final Map<DexProgramClass, ProgramMethodSet> newSyntheticMethods =
       new IdentityHashMap<>();
 
   ClassProcessor(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       InterfaceMethodRewriter rewriter,
-      Consumer<DexEncodedMethod> newSynthesizedMethodConsumer) {
+      Consumer<ProgramMethod> newSynthesizedMethodConsumer) {
     this.appView = appView;
     this.dexItemFactory = appView.dexItemFactory();
     this.rewriter = rewriter;
@@ -219,13 +221,11 @@
   }
 
   final void addSyntheticMethods() {
-    for (DexProgramClass clazz : newSyntheticMethods.keySet()) {
-      List<DexEncodedMethod> newForwardingMethods = newSyntheticMethods.get(clazz);
-      if (newForwardingMethods != null) {
-        clazz.addVirtualMethods(newForwardingMethods);
-        newForwardingMethods.forEach(newSynthesizedMethodConsumer);
-      }
-    }
+    newSyntheticMethods.forEach(
+        (clazz, newForwardingMethods) -> {
+          clazz.addVirtualMethods(newForwardingMethods.toDefinitionSet());
+          newForwardingMethods.forEach(newSynthesizedMethodConsumer);
+        });
   }
 
   // Computes the set of method signatures that may need forwarding methods on derived classes.
@@ -352,8 +352,10 @@
 
   // Construction of actual forwarding methods.
 
-  private void addSyntheticMethod(DexProgramClass clazz, DexEncodedMethod newMethod) {
-    newSyntheticMethods.computeIfAbsent(clazz, key -> new ArrayList<>()).add(newMethod);
+  private void addSyntheticMethod(DexProgramClass clazz, DexEncodedMethod method) {
+    newSyntheticMethods
+        .computeIfAbsent(clazz, key -> ProgramMethodSet.create())
+        .createAndAdd(clazz, method);
   }
 
   private void addICCEThrowingMethod(DexMethod method, DexClass clazz) {
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 b5c3f3e..4d5c7be 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
@@ -13,6 +13,7 @@
 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.DexType;
 import com.android.tools.r8.graph.DexValue;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
@@ -63,7 +65,7 @@
     // List of methods that should be added to the next class.
     List<DexEncodedMethod> methodsWithCovariantReturnTypeAnnotation = new LinkedList<>();
     List<DexEncodedMethod> covariantReturnTypeMethods = new LinkedList<>();
-    for (DexClass clazz : builder.getProgramClasses()) {
+    for (DexProgramClass clazz : builder.getProgramClasses()) {
       // Construct the methods that should be added to clazz.
       buildCovariantReturnTypeMethodsForClass(
           clazz, methodsWithCovariantReturnTypeAnnotation, covariantReturnTypeMethods);
@@ -106,7 +108,7 @@
   // CovariantReturnTypes annotations in the given DexClass. Adds the newly constructed, synthetic
   // methods to the list covariantReturnTypeMethods.
   private void buildCovariantReturnTypeMethodsForClass(
-      DexClass clazz,
+      DexProgramClass clazz,
       List<DexEncodedMethod> methodsWithCovariantReturnTypeAnnotation,
       List<DexEncodedMethod> covariantReturnTypeMethods) {
     for (DexEncodedMethod method : clazz.virtualMethods()) {
@@ -130,7 +132,9 @@
   // variantReturnTypes annotations on the given method. Adds the newly constructed, synthetic
   // methods to the list covariantReturnTypeMethods.
   private void buildCovariantReturnTypeMethodsForMethod(
-      DexClass clazz, DexEncodedMethod method, List<DexEncodedMethod> covariantReturnTypeMethods) {
+      DexProgramClass clazz,
+      DexEncodedMethod method,
+      List<DexEncodedMethod> covariantReturnTypeMethods) {
     assert methodHasCovariantReturnTypeAnnotation(method);
     for (DexType covariantReturnType : getCovariantReturnTypes(clazz, method)) {
       DexEncodedMethod covariantReturnTypeMethod =
@@ -145,7 +149,7 @@
   //
   // Note: any "synchronized" or "strictfp" modifier could be dropped safely.
   private DexEncodedMethod buildCovariantReturnTypeMethod(
-      DexClass clazz, DexEncodedMethod method, DexType covariantReturnType) {
+      DexProgramClass clazz, DexEncodedMethod method, DexType covariantReturnType) {
     DexProto newProto =
         factory.createProto(
             covariantReturnType, method.method.proto.parameters, method.method.proto.shorty);
@@ -170,7 +174,8 @@
             new SynthesizedCode(forwardSourceCodeBuilder::build),
             true);
     // Optimize to generate DexCode instead of SynthesizedCode.
-    converter.optimizeSynthesizedMethod(newVirtualMethod);
+    ProgramMethod programMethod = new ProgramMethod(clazz, newVirtualMethod);
+    converter.optimizeSynthesizedMethod(programMethod);
     return newVirtualMethod;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
index 738f4aa..f691760 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
@@ -10,7 +10,9 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -21,6 +23,7 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -41,20 +44,17 @@
 
   // Maps a nest host to a class met which has that nest host.
   // The value is used because the nest host might be missing.
-  private final Map<DexType, DexClass> metNestHosts = new ConcurrentHashMap<>();
+  private final Map<DexType, DexProgramClass> metNestHosts = new ConcurrentHashMap<>();
 
   public D8NestBasedAccessDesugaring(AppView<?> appView) {
     super(appView);
   }
 
-  public void rewriteNestBasedAccesses(
-      DexEncodedMethod encodedMethod, IRCode code, AppView<?> appView) {
-    DexClass currentClass = appView.definitionFor(encodedMethod.holder());
-    assert currentClass != null;
-    if (!currentClass.isInANest()) {
+  public void rewriteNestBasedAccesses(ProgramMethod method, IRCode code, AppView<?> appView) {
+    if (!method.getHolder().isInANest()) {
       return;
     }
-    metNestHosts.put(currentClass.getNestHost(), currentClass);
+    metNestHosts.put(method.getHolder().getNestHost(), method.getHolder());
 
     ListIterator<BasicBlock> blocks = code.listIterator();
     while (blocks.hasNext()) {
@@ -67,8 +67,7 @@
           DexMethod methodCalled = invokeMethod.getInvokedMethod();
           DexEncodedMethod encodedMethodCalled =
               methodCalled.holder.isClassType() ? appView.definitionFor(methodCalled) : null;
-          if (encodedMethodCalled != null
-              && invokeRequiresRewriting(encodedMethodCalled, currentClass)) {
+          if (encodedMethodCalled != null && invokeRequiresRewriting(encodedMethodCalled, method)) {
             DexMethod bridge = ensureInvokeBridge(encodedMethodCalled);
             if (encodedMethodCalled.isInstanceInitializer()) {
               instructions.previous();
@@ -87,7 +86,7 @@
         } else if (instruction.isFieldInstruction()) {
           DexEncodedField encodedField =
               appView.definitionFor(instruction.asFieldInstruction().getField());
-          if (encodedField != null && fieldAccessRequiresRewriting(encodedField, currentClass)) {
+          if (encodedField != null && fieldAccessRequiresRewriting(encodedField, method)) {
             if (instruction.isInstanceGet() || instruction.isStaticGet()) {
               DexMethod bridge = ensureFieldAccessBridge(encodedField, true);
               instructions.replaceCurrentInstruction(
@@ -106,7 +105,7 @@
 
   private void processNestsConcurrently(ExecutorService executorService) throws ExecutionException {
     List<Future<?>> futures = new ArrayList<>();
-    for (DexClass clazz : metNestHosts.values()) {
+    for (DexProgramClass clazz : metNestHosts.values()) {
       futures.add(asyncProcessNest(clazz, executorService));
     }
     ThreadUtils.awaitFutures(futures);
@@ -118,17 +117,15 @@
     addDeferredBridges(putFieldBridges.values());
   }
 
-  private void addDeferredBridges(Collection<DexEncodedMethod> bridges) {
-    for (DexEncodedMethod bridge : bridges) {
-      DexClass holder = definitionFor(bridge.holder());
-      assert holder != null && holder.isProgramClass();
-      holder.asProgramClass().addMethod(bridge);
+  private void addDeferredBridges(Collection<ProgramMethod> bridges) {
+    for (ProgramMethod bridge : bridges) {
+      bridge.getHolder().addMethod(bridge.getDefinition());
     }
   }
 
   private void optimizeDeferredBridgesConcurrently(
       ExecutorService executorService, IRConverter converter) throws ExecutionException {
-    Collection<DexEncodedMethod> methods = new ArrayList<>();
+    ProgramMethodSet methods = ProgramMethodSet.create();
     methods.addAll(bridges.values());
     methods.addAll(getFieldBridges.values());
     methods.addAll(putFieldBridges.values());
@@ -147,7 +144,7 @@
   // In D8, programClass are processed on the fly so they do not need to be processed again here.
   @Override
   protected boolean shouldProcessClassInNest(DexClass clazz, List<DexType> nest) {
-    return clazz.isNotProgramClass();
+    return clazz.isClasspathClass();
   }
 
   @Override
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 b9913cc..ca7fdec 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
@@ -15,6 +15,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.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -29,14 +30,13 @@
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.WorkList;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.ListIterator;
@@ -72,7 +72,7 @@
   private final Mode mode;
   private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
   private final Map<DexClass, Set<DexEncodedMethod>> callBackMethods = new IdentityHashMap<>();
-  private final Map<DexClass, List<DexEncodedMethod>> pendingCallBackMethods =
+  private final Map<DexProgramClass, List<DexEncodedMethod>> pendingCallBackMethods =
       new IdentityHashMap<>();
   private final Set<DexMethod> trackedCallBackAPIs;
   private final Set<DexMethod> trackedAPIs;
@@ -111,9 +111,9 @@
     }
 
     if (!canGenerateWrappersAndCallbacks()) {
-      assert validateCallbackWasGeneratedInEnqueuer(code.method());
+      assert validateCallbackWasGeneratedInEnqueuer(code.context());
     } else {
-      registerCallbackIfRequired(code.method());
+      registerCallbackIfRequired(code.context());
     }
 
     ListIterator<BasicBlock> blockIterator = code.listIterator();
@@ -136,14 +136,12 @@
     }
   }
 
-  private boolean validateCallbackWasGeneratedInEnqueuer(DexEncodedMethod encodedMethod) {
-    if (!shouldRegisterCallback(encodedMethod)) {
+  private boolean validateCallbackWasGeneratedInEnqueuer(ProgramMethod method) {
+    if (!shouldRegisterCallback(method)) {
       return true;
     }
-    DexProgramClass holderClass = appView.definitionForProgramType(encodedMethod.holder());
-    DexMethod installedCallback =
-        methodWithVivifiedTypeInSignature(encodedMethod.method, holderClass.type, appView);
-    assert holderClass.lookupMethod(installedCallback) != null;
+    DexMethod installedCallback = methodWithVivifiedTypeInSignature(method, appView);
+    assert method.getHolder().lookupMethod(installedCallback) != null;
     return true;
   }
 
@@ -159,15 +157,13 @@
     return appView.rewritePrefix.hasRewrittenTypeInSignature(invokedMethod.proto, appView);
   }
 
-  public void registerCallbackIfRequired(DexEncodedMethod encodedMethod) {
-    if (shouldRegisterCallback(encodedMethod)) {
-      DexClass dexClass = appView.definitionFor(encodedMethod.holder());
-      assert dexClass != null;
-      registerCallback(dexClass, encodedMethod);
+  public void registerCallbackIfRequired(ProgramMethod method) {
+    if (shouldRegisterCallback(method)) {
+      registerCallback(method);
     }
   }
 
-  private boolean shouldRegisterCallback(DexEncodedMethod encodedMethod) {
+  private boolean shouldRegisterCallback(ProgramMethod method) {
     // Any override of a library method can be called by the library.
     // We duplicate the method to have a vivified type version callable by the library and
     // a type version callable by the program. We need to add the vivified version to the rootset
@@ -175,36 +171,32 @@
     // library type), but the enqueuer cannot see that.
     // To avoid too much computation we first look if the method would need to be rewritten if
     // it would override a library method, then check if it overrides a library method.
-    if (encodedMethod.isPrivateMethod()
-        || encodedMethod.isStatic()
-        || encodedMethod.isLibraryMethodOverride().isFalse()) {
+    DexEncodedMethod definition = method.getDefinition();
+    if (definition.isPrivateMethod()
+        || definition.isStatic()
+        || definition.isLibraryMethodOverride().isFalse()) {
       return false;
     }
-    DexMethod method = encodedMethod.method;
-    if (method.holder.isArrayType()
-        || !appView.rewritePrefix.hasRewrittenTypeInSignature(method.proto, appView)
+    if (!appView.rewritePrefix.hasRewrittenTypeInSignature(definition.proto(), appView)
         || appView
             .options()
             .desugaredLibraryConfiguration
             .getEmulateLibraryInterface()
-            .containsKey(method.holder)) {
+            .containsKey(method.getHolderType())) {
       return false;
     }
-    DexClass dexClass = appView.definitionFor(method.holder);
-    if (dexClass == null) {
-      return false;
-    }
-    return overridesLibraryMethod(dexClass, method);
+    return overridesLibraryMethod(method);
   }
 
-  private boolean overridesLibraryMethod(DexClass theClass, DexMethod method) {
+  private boolean overridesLibraryMethod(ProgramMethod method) {
     // We look up everywhere to see if there is a supertype/interface implementing the method...
+    DexProgramClass holder = method.getHolder();
     WorkList<DexType> workList = WorkList.newIdentityWorkList();
-    workList.addIfNotSeen(theClass.interfaces.values);
+    workList.addIfNotSeen(holder.interfaces.values);
     boolean foundOverrideToRewrite = false;
     // There is no methods with desugared types on Object.
-    if (theClass.superType != factory.objectType) {
-      workList.addIfNotSeen(theClass.superType);
+    if (holder.superType != factory.objectType) {
+      workList.addIfNotSeen(holder.superType);
     }
     while (workList.hasNext()) {
       DexType current = workList.next();
@@ -219,7 +211,7 @@
       if (!dexClass.isLibraryClass() && !appView.options().isDesugaredLibraryCompilation()) {
         continue;
       }
-      DexEncodedMethod dexEncodedMethod = dexClass.lookupVirtualMethod(method);
+      DexEncodedMethod dexEncodedMethod = dexClass.lookupVirtualMethod(method.getReference());
       if (dexEncodedMethod != null) {
         // In this case, the object will be wrapped.
         if (appView.rewritePrefix.hasRewrittenType(dexClass.type, appView)) {
@@ -231,34 +223,41 @@
     return foundOverrideToRewrite;
   }
 
-  private synchronized void registerCallback(DexClass dexClass, DexEncodedMethod originalMethod) {
+  private synchronized void registerCallback(ProgramMethod method) {
     // In R8 we should be in the enqueuer, therefore we can duplicate a default method and both
     // methods will be desugared.
     // In D8, this happens after interface method desugaring, we cannot introduce new default
     // methods, but we do not need to since this is a library override (invokes will resolve) and
     // all implementors have been enhanced with a forwarding method which will be duplicated.
     if (!appView.enableWholeProgramOptimizations()) {
-      if (dexClass.isInterface()
-          && originalMethod.isDefaultMethod()
+      if (method.getHolder().isInterface()
+          && method.getDefinition().isDefaultMethod()
           && (!appView.options().canUseDefaultAndStaticInterfaceMethods()
               || appView.options().isDesugaredLibraryCompilation())) {
         return;
       }
     }
     if (trackedCallBackAPIs != null) {
-      trackedCallBackAPIs.add(originalMethod.method);
+      trackedCallBackAPIs.add(method.getReference());
     }
-    addCallBackSignature(dexClass, originalMethod);
+    addCallBackSignature(method);
   }
 
-  private synchronized void addCallBackSignature(DexClass dexClass, DexEncodedMethod method) {
-    assert dexClass.type == method.holder();
-    if (callBackMethods.computeIfAbsent(dexClass, key -> new HashSet<>()).add(method)) {
-      pendingCallBackMethods.computeIfAbsent(dexClass, key -> new ArrayList<>()).add(method);
+  private synchronized void addCallBackSignature(ProgramMethod method) {
+    DexProgramClass holder = method.getHolder();
+    DexEncodedMethod definition = method.getDefinition();
+    if (callBackMethods.computeIfAbsent(holder, key -> Sets.newIdentityHashSet()).add(definition)) {
+      pendingCallBackMethods.computeIfAbsent(holder, key -> new ArrayList<>()).add(definition);
     }
   }
 
   public static DexMethod methodWithVivifiedTypeInSignature(
+      ProgramMethod method, AppView<?> appView) {
+    return methodWithVivifiedTypeInSignature(
+        method.getReference(), method.getHolderType(), appView);
+  }
+
+  public static DexMethod methodWithVivifiedTypeInSignature(
       DexMethod originalMethod, DexType holder, AppView<?> appView) {
     DexType[] newParameters = originalMethod.proto.parameters.values.clone();
     int index = 0;
@@ -285,28 +284,32 @@
     if (appView.enableWholeProgramOptimizations()) {
       return;
     }
-    List<DexEncodedMethod> callbacks = generateCallbackMethods();
+    ProgramMethodSet callbacks = generateCallbackMethods();
     irConverter.processMethodsConcurrently(callbacks, executorService);
     wrapperSynthesizor.finalizeWrappersForD8(builder, irConverter, executorService);
   }
 
-  public List<DexEncodedMethod> generateCallbackMethods() {
+  public ProgramMethodSet generateCallbackMethods() {
     if (appView.options().testing.trackDesugaredAPIConversions) {
       generateTrackDesugaredAPIWarnings(trackedAPIs, "");
       generateTrackDesugaredAPIWarnings(trackedCallBackAPIs, "callback ");
       trackedAPIs.clear();
       trackedCallBackAPIs.clear();
     }
-    List<DexEncodedMethod> result = new ArrayList<>();
+    ProgramMethodSet allCallbackMethods = ProgramMethodSet.create();
     pendingCallBackMethods.forEach(
         (clazz, callbacks) -> {
-          List<DexEncodedMethod> generated =
-              ListUtils.map(callbacks, callback -> generateCallbackMethod(callback, clazz));
-          clazz.addVirtualMethods(generated);
-          result.addAll(generated);
+          List<DexEncodedMethod> newVirtualMethods = new ArrayList<>();
+          callbacks.forEach(
+              callback -> {
+                ProgramMethod callbackMethod = generateCallbackMethod(callback, clazz);
+                newVirtualMethods.add(callbackMethod.getDefinition());
+                allCallbackMethods.add(callbackMethod);
+              });
+          clazz.addVirtualMethods(newVirtualMethods);
         });
     pendingCallBackMethods.clear();
-    return result;
+    return allCallbackMethods;
   }
 
   public List<DexProgramClass> synthesizeWrappers(
@@ -319,21 +322,21 @@
     return wrapperSynthesizor.synthesizeClasspathMock(classToMock, mockType, mockIsInterface);
   }
 
-  private DexEncodedMethod generateCallbackMethod(
-      DexEncodedMethod originalMethod, DexClass dexClass) {
+  private ProgramMethod generateCallbackMethod(
+      DexEncodedMethod originalMethod, DexProgramClass clazz) {
     DexMethod methodToInstall =
-        methodWithVivifiedTypeInSignature(originalMethod.method, dexClass.type, appView);
+        methodWithVivifiedTypeInSignature(originalMethod.method, clazz.type, appView);
     CfCode cfCode =
         new APIConverterWrapperCfCodeProvider(
-                appView, originalMethod.method, null, this, dexClass.isInterface())
+                appView, originalMethod.method, null, this, clazz.isInterface())
             .generateCfCode();
-    DexEncodedMethod newDexEncodedMethod =
+    DexEncodedMethod newMethod =
         wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
-    newDexEncodedMethod.setCode(cfCode, appView);
+    newMethod.setCode(cfCode, appView);
     if (originalMethod.isLibraryMethodOverride().isTrue()) {
-      newDexEncodedMethod.setLibraryMethodOverride(OptionalBool.TRUE);
+      newMethod.setLibraryMethodOverride(OptionalBool.TRUE);
     }
-    return newDexEncodedMethod;
+    return new ProgramMethod(clazz, newMethod);
   }
 
   private void generateTrackDesugaredAPIWarnings(Set<DexMethod> tracked, String inner) {
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 ecf9d2e..a640fb6 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
@@ -30,6 +30,7 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -274,28 +275,27 @@
         map.putIfAbsent(emulatedDispatchMethod.holder, new ArrayList<>(1));
         map.get(emulatedDispatchMethod.holder).add(emulatedDispatchMethod);
       }
-      List<DexEncodedMethod> addedMethods = new ArrayList<>();
+      ProgramMethodSet addedMethods = ProgramMethodSet.create();
       for (DexProgramClass clazz : appView.appInfo().classes()) {
         if (clazz.superType == null) {
           assert clazz.type == appView.dexItemFactory().objectType : clazz.type.toSourceString();
           continue;
         }
-        DexClass dexClass = appView.definitionFor(clazz.superType);
+        DexClass superclass = appView.definitionFor(clazz.superType);
         // Only performs computation if superclass is a library class, but not object to filter out
         // the most common case.
-        if (dexClass != null
-            && dexClass.isLibraryClass()
-            && dexClass.type != appView.dexItemFactory().objectType) {
-          for (DexType dexType : map.keySet()) {
-            if (inherit(dexClass.asLibraryClass(), dexType, emulatedDispatchMethods)) {
-              addedMethods.addAll(addInterfacesAndForwardingMethods(clazz, map.get(dexType)));
-            }
-          }
+        if (superclass != null
+            && superclass.isLibraryClass()
+            && superclass.type != appView.dexItemFactory().objectType) {
+          map.forEach(
+              (type, methods) -> {
+                if (inherit(superclass.asLibraryClass(), type, emulatedDispatchMethods)) {
+                  addInterfacesAndForwardingMethods(
+                      clazz, methods, method -> addedMethods.createAndAdd(clazz, method));
+                }
+              });
         }
       }
-      if (addedMethods.isEmpty()) {
-        return;
-      }
       converter.processMethodsConcurrently(addedMethods, executorService);
     }
 
@@ -318,14 +318,15 @@
       return false;
     }
 
-    private List<DexEncodedMethod> addInterfacesAndForwardingMethods(
-        DexProgramClass clazz, List<DexMethod> dexMethods) {
+    private void addInterfacesAndForwardingMethods(
+        DexProgramClass clazz,
+        List<DexMethod> methods,
+        Consumer<DexEncodedMethod> newForwardingMethodsConsumer) {
       // DesugaredLibraryRetargeter emulate dispatch: insertion of a marker interface & forwarding
       // methods.
       // We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
       // applies up to 24.
-      List<DexEncodedMethod> newForwardingMethods = new ArrayList<>();
-      for (DexMethod dexMethod : dexMethods) {
+      for (DexMethod dexMethod : methods) {
         DexType[] newInterfaces =
             Arrays.copyOf(clazz.interfaces.values, clazz.interfaces.size() + 1);
         newInterfaces[newInterfaces.length - 1] = dispatchInterfaceTypeFor(dexMethod);
@@ -334,10 +335,9 @@
         if (dexEncodedMethod == null) {
           DexEncodedMethod newMethod = createForwardingMethod(dexMethod, clazz);
           clazz.addVirtualMethod(newMethod);
-          newForwardingMethods.add(newMethod);
+          newForwardingMethodsConsumer.accept(newMethod);
         }
       }
-      return newForwardingMethods;
     }
 
     private DexEncodedMethod createForwardingMethod(DexMethod target, DexClass clazz) {
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 6467824..5d494bb 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
@@ -28,6 +28,7 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -46,6 +47,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -110,7 +112,7 @@
 
   // All forwarding methods generated during desugaring. We don't synchronize access
   // to this collection since it is only filled in ClassProcessor running synchronously.
-  private final Set<DexEncodedMethod> synthesizedMethods = Sets.newIdentityHashSet();
+  private final ProgramMethodSet synthesizedMethods = ProgramMethodSet.create();
 
   // Caches default interface method info for already processed interfaces.
   private final Map<DexType, DefaultMethodsHelper.Collection> cache = new ConcurrentHashMap<>();
@@ -675,28 +677,30 @@
     emulateLibraryClassFlags.setFinal();
     emulateLibraryClassFlags.setSynthetic();
     emulateLibraryClassFlags.setPublic();
-    synthesizedMethods.addAll(emulationMethods);
-    return new DexProgramClass(
-        emulateLibraryClassType,
-        null,
-        new SynthesizedOrigin("interface desugaring (libs)", getClass()),
-        emulateLibraryClassFlags,
-        factory.objectType,
-        DexTypeList.empty(),
-        theInterface.sourceFile,
-        null,
-        Collections.emptyList(),
-        null,
-        Collections.emptyList(),
-        DexAnnotationSet.empty(),
-        DexEncodedField.EMPTY_ARRAY,
-        DexEncodedField.EMPTY_ARRAY,
-        // All synthesized methods are static in this case.
-        emulationMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
-        DexEncodedMethod.EMPTY_ARRAY,
-        factory.getSkipNameValidationForTesting(),
-        DexProgramClass::checksumFromType,
-        Collections.singletonList(theInterface));
+    DexProgramClass clazz =
+        new DexProgramClass(
+            emulateLibraryClassType,
+            null,
+            new SynthesizedOrigin("interface desugaring (libs)", getClass()),
+            emulateLibraryClassFlags,
+            factory.objectType,
+            DexTypeList.empty(),
+            theInterface.sourceFile,
+            null,
+            Collections.emptyList(),
+            null,
+            Collections.emptyList(),
+            DexAnnotationSet.empty(),
+            DexEncodedField.EMPTY_ARRAY,
+            DexEncodedField.EMPTY_ARRAY,
+            // All synthesized methods are static in this case.
+            emulationMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
+            DexEncodedMethod.EMPTY_ARRAY,
+            factory.getSkipNameValidationForTesting(),
+            DexProgramClass::checksumFromType,
+            Collections.singletonList(theInterface));
+    clazz.forEachProgramMethod(synthesizedMethods::add);
+    return clazz;
   }
 
   private static String getEmulateLibraryInterfaceClassDescriptor(String descriptor) {
@@ -1020,7 +1024,8 @@
       }
     }
     for (Entry<DexLibraryClass, Set<DexProgramClass>> entry : requiredDispatchClasses.entrySet()) {
-      synthesizedMethods.addAll(processor.process(entry.getKey(), entry.getValue()));
+      DexProgramClass dispatchClass = processor.process(entry.getKey(), entry.getValue());
+      dispatchClass.forEachProgramMethod(synthesizedMethods::add);
     }
     if (appView.enableWholeProgramOptimizations()) {
       appView.setGraphLense(graphLensBuilder.build(appView.dexItemFactory(), appView.graphLense()));
@@ -1029,7 +1034,7 @@
   }
 
   private void processClasses(
-      Builder<?> builder, Flavor flavour, Consumer<DexEncodedMethod> newSynthesizedMethodConsumer) {
+      Builder<?> builder, Flavor flavour, Consumer<ProgramMethod> newSynthesizedMethodConsumer) {
     ClassProcessor processor = new ClassProcessor(appView, this, newSynthesizedMethodConsumer);
     // First we compute all desugaring *without* introducing forwarding methods.
     for (DexProgramClass clazz : builder.getProgramClasses()) {
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 ab5a9cd..f1a499a 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
@@ -232,7 +232,7 @@
     return c -> 7 * checksum;
   }
 
-  List<DexEncodedMethod> process(DexLibraryClass iface, Set<DexProgramClass> callers) {
+  DexProgramClass process(DexLibraryClass iface, Set<DexProgramClass> callers) {
     assert iface.isInterface();
 
     // The list of methods to be created in dispatch class.
@@ -305,7 +305,7 @@
             DexProgramClass::checksumFromType,
             callers);
     syntheticClasses.put(iface.type, dispatchClass);
-    return dispatchMethods;
+    return dispatchClass;
   }
 
   private boolean canMoveToCompanionClass(DexEncodedMethod method) {
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 88d21d4..05c9c66 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
@@ -512,7 +512,7 @@
     final Invoke.Type invokeType;
 
     private boolean hasEnsuredAccessibility;
-    private DexEncodedMethod accessibilityBridge;
+    private ProgramMethod accessibilityBridge;
 
     Target(DexMethod callTarget, Invoke.Type invokeType) {
       assert callTarget != null;
@@ -522,10 +522,10 @@
     }
 
     // Ensure access of the referenced symbol(s).
-    abstract DexEncodedMethod ensureAccessibility(boolean allowMethodModification);
+    abstract ProgramMethod ensureAccessibility(boolean allowMethodModification);
 
     // Ensure access of the referenced symbol(s).
-    public DexEncodedMethod ensureAccessibilityIfNeeded(boolean allowMethodModification) {
+    public ProgramMethod ensureAccessibilityIfNeeded(boolean allowMethodModification) {
       if (!hasEnsuredAccessibility) {
         accessibilityBridge = ensureAccessibility(allowMethodModification);
         hasEnsuredAccessibility = true;
@@ -552,7 +552,7 @@
       // The only case where we do Lambda desugaring with Cf to Cf is in L8.
       // If the compilation is not coreLibraryCompilation, then the assertion
       // implMethodHolder != null may fail, hence the assertion.
-      assert options.isDesugaredLibraryCompilation() || options.enableCfInterfaceMethodDesugaring;
+      assert options.cfToCfDesugar;
       DexMethod implMethod = descriptor.implHandle.asMethod();
       DexClass implMethodHolder = definitionFor(implMethod.holder);
       if (implMethodHolder == null) {
@@ -574,7 +574,7 @@
     }
 
     @Override
-    DexEncodedMethod ensureAccessibility(boolean allowMethodModification) {
+    ProgramMethod ensureAccessibility(boolean allowMethodModification) {
       return null;
     }
   }
@@ -590,7 +590,7 @@
     }
 
     @Override
-    DexEncodedMethod ensureAccessibility(boolean allowMethodModification) {
+    ProgramMethod ensureAccessibility(boolean allowMethodModification) {
       // We already found the static method to be called, just relax its accessibility.
       target.getDefinition().accessFlags.unsetPrivate();
       if (target.getHolder().isInterface()) {
@@ -609,11 +609,11 @@
     }
 
     @Override
-    DexEncodedMethod ensureAccessibility(boolean allowMethodModification) {
+    ProgramMethod ensureAccessibility(boolean allowMethodModification) {
       // For all instantiation points for which the compiler creates lambda$
       // methods, it creates these methods in the same class/interface.
       DexMethod implMethod = descriptor.implHandle.asMethod();
-      DexClass implMethodHolder = definitionFor(implMethod.holder);
+      DexProgramClass implMethodHolder = definitionFor(implMethod.holder).asProgramClass();
 
       DexEncodedMethod replacement =
           implMethodHolder
@@ -649,7 +649,7 @@
       assert replacement != null
           : "Unexpected failure to find direct lambda target for: " + implMethod.qualifiedName();
 
-      return replacement;
+      return new ProgramMethod(implMethodHolder, replacement);
     }
   }
 
@@ -661,7 +661,7 @@
     }
 
     @Override
-    DexEncodedMethod ensureAccessibility(boolean allowMethodModification) {
+    ProgramMethod ensureAccessibility(boolean allowMethodModification) {
       // When compiling with whole program optimization, check that we are not inplace modifying.
       assert !(rewriter.getAppView().enableWholeProgramOptimizations() && allowMethodModification);
       // For all instantiation points for which the compiler creates lambda$
@@ -673,34 +673,37 @@
           : createSyntheticAccessor(implMethod, implMethodHolder);
     }
 
-    private DexEncodedMethod modifyLambdaImplementationMethod(
+    private ProgramMethod modifyLambdaImplementationMethod(
         DexMethod implMethod, DexProgramClass implMethodHolder) {
-      return implMethodHolder
-          .getMethodCollection()
-          .replaceDirectMethodWithVirtualMethod(
-              implMethod,
-              encodedMethod -> {
-                assert encodedMethod.isDirectMethod();
-                // We need to create a new method with the same code to be able to safely relax its
-                // accessibility and make it virtual.
-                MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy();
-                newAccessFlags.unsetPrivate();
-                newAccessFlags.setPublic();
-                DexEncodedMethod newMethod =
-                    new DexEncodedMethod(
-                        callTarget,
-                        newAccessFlags,
-                        encodedMethod.annotations(),
-                        encodedMethod.parameterAnnotationsList,
-                        encodedMethod.getCode(),
-                        true);
-                newMethod.copyMetadata(encodedMethod);
-                rewriter.originalMethodSignatures.put(callTarget, encodedMethod.method);
-                return newMethod;
-              });
+      DexEncodedMethod replacement =
+          implMethodHolder
+              .getMethodCollection()
+              .replaceDirectMethodWithVirtualMethod(
+                  implMethod,
+                  encodedMethod -> {
+                    assert encodedMethod.isDirectMethod();
+                    // We need to create a new method with the same code to be able to safely relax
+                    // its
+                    // accessibility and make it virtual.
+                    MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy();
+                    newAccessFlags.unsetPrivate();
+                    newAccessFlags.setPublic();
+                    DexEncodedMethod newMethod =
+                        new DexEncodedMethod(
+                            callTarget,
+                            newAccessFlags,
+                            encodedMethod.annotations(),
+                            encodedMethod.parameterAnnotationsList,
+                            encodedMethod.getCode(),
+                            true);
+                    newMethod.copyMetadata(encodedMethod);
+                    rewriter.originalMethodSignatures.put(callTarget, encodedMethod.method);
+                    return newMethod;
+                  });
+      return new ProgramMethod(implMethodHolder, replacement);
     }
 
-    private DexEncodedMethod createSyntheticAccessor(
+    private ProgramMethod createSyntheticAccessor(
         DexMethod implMethod, DexProgramClass implMethodHolder) {
       MethodAccessFlags accessorFlags =
           MethodAccessFlags.fromSharedAccessFlags(
@@ -726,7 +729,7 @@
               true);
 
       implMethodHolder.addVirtualMethod(accessorEncodedMethod);
-      return accessorEncodedMethod;
+      return new ProgramMethod(implMethodHolder, accessorEncodedMethod);
     }
   }
 
@@ -739,7 +742,7 @@
     }
 
     @Override
-    DexEncodedMethod ensureAccessibility(boolean allowMethodModification) {
+    ProgramMethod ensureAccessibility(boolean allowMethodModification) {
       // Create a static accessor with proper accessibility.
       DexProgramClass accessorClass = programDefinitionFor(callTarget.holder);
       assert accessorClass != null;
@@ -764,7 +767,7 @@
         accessorClass.addDirectMethod(accessorEncodedMethod);
       }
 
-      return accessorEncodedMethod;
+      return new ProgramMethod(accessorClass, accessorEncodedMethod);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 4236e52..6b41818 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -29,6 +30,7 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableSet;
@@ -115,11 +117,12 @@
   private void synthesizeAccessibilityBridgesForLambdaClassesD8(
       Collection<LambdaClass> lambdaClasses, IRConverter converter, ExecutorService executorService)
       throws ExecutionException {
-    Set<DexEncodedMethod> nonDexAccessibilityBridges = Sets.newIdentityHashSet();
+    ProgramMethodSet nonDexAccessibilityBridges = ProgramMethodSet.create();
     for (LambdaClass lambdaClass : lambdaClasses) {
       // This call may cause originalMethodSignatures to be updated.
-      DexEncodedMethod accessibilityBridge = lambdaClass.target.ensureAccessibilityIfNeeded(true);
-      if (accessibilityBridge != null && !accessibilityBridge.getCode().isDexCode()) {
+      ProgramMethod accessibilityBridge = lambdaClass.target.ensureAccessibilityIfNeeded(true);
+      if (accessibilityBridge != null
+          && !accessibilityBridge.getDefinition().getCode().isDexCode()) {
         nonDexAccessibilityBridges.add(accessibilityBridge);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
index f4e1f4a..7da225c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
@@ -4,13 +4,16 @@
 
 package com.android.tools.r8.ir.desugar;
 
+
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.ClasspathMethod;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -21,6 +24,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.origin.SynthesizedOrigin;
@@ -55,9 +59,9 @@
   protected final AppView<?> appView;
   // Following maps are there to avoid creating the bridges multiple times
   // and remember the bridges to add once the nests are processed.
-  final Map<DexMethod, DexEncodedMethod> bridges = new ConcurrentHashMap<>();
-  final Map<DexField, DexEncodedMethod> getFieldBridges = new ConcurrentHashMap<>();
-  final Map<DexField, DexEncodedMethod> putFieldBridges = new ConcurrentHashMap<>();
+  final Map<DexMethod, ProgramMethod> bridges = new ConcurrentHashMap<>();
+  final Map<DexField, ProgramMethod> getFieldBridges = new ConcurrentHashMap<>();
+  final Map<DexField, ProgramMethod> putFieldBridges = new ConcurrentHashMap<>();
   // Common single empty class for nest based private constructors
   private final DexProgramClass nestConstructor;
   private boolean nestConstructorUsed = false;
@@ -81,9 +85,9 @@
   }
 
   private DexEncodedMethod definitionFor(
-      DexMethod method, DexMethod context, Invoke.Type invokeType) {
+      DexMethod method, DexClassAndMethod context, Invoke.Type invokeType) {
     return appView.definitionFor(
-        appView.graphLense().lookupMethod(method, context, invokeType).getMethod());
+        appView.graphLense().lookupMethod(method, context.getReference(), invokeType).getMethod());
   }
 
   private DexEncodedField definitionFor(DexField field) {
@@ -109,7 +113,7 @@
     return new Pair<>(hostClass, classesInNest);
   }
 
-  Future<?> asyncProcessNest(DexClass clazz, ExecutorService executorService) {
+  Future<?> asyncProcessNest(DexProgramClass clazz, ExecutorService executorService) {
     return executorService.submit(
         () -> {
           Pair<DexClass, List<DexType>> nest = extractNest(clazz);
@@ -133,11 +137,17 @@
       } else {
         reportDesugarDependencies(host, clazz);
         if (shouldProcessClassInNest(clazz, nest)) {
-          NestBasedAccessDesugaringUseRegistry registry =
-              new NestBasedAccessDesugaringUseRegistry(clazz);
-          for (DexEncodedMethod method : clazz.methods()) {
-            registry.setContext(method.method);
-            method.registerCodeReferences(registry);
+          for (DexEncodedMethod definition : clazz.methods()) {
+            if (clazz.isProgramClass()) {
+              ProgramMethod method = new ProgramMethod(clazz.asProgramClass(), definition);
+              method.registerCodeReferences(new NestBasedAccessDesugaringUseRegistry(method));
+            } else if (clazz.isClasspathClass()) {
+              ClasspathMethod method = new ClasspathMethod(clazz.asClasspathClass(), definition);
+              method.registerCodeReferencesForDesugaring(
+                  new NestBasedAccessDesugaringUseRegistry(method));
+            } else {
+              assert false;
+            }
           }
         }
       }
@@ -255,26 +265,26 @@
         .createMethod(holderType, proto, computeFieldBridgeName(field, isGet));
   }
 
-  boolean invokeRequiresRewriting(DexEncodedMethod method, DexClass contextClass) {
+  boolean invokeRequiresRewriting(DexEncodedMethod method, DexClassAndMethod context) {
     assert method != null;
     // Rewrite only when targeting other nest members private fields.
-    if (!method.accessFlags.isPrivate() || method.holder() == contextClass.type) {
+    if (!method.accessFlags.isPrivate() || method.holder() == context.getHolderType()) {
       return false;
     }
     DexClass methodHolder = definitionFor(method.holder());
     assert methodHolder != null; // from encodedMethod
-    return methodHolder.getNestHost() == contextClass.getNestHost();
+    return methodHolder.getNestHost() == context.getHolder().getNestHost();
   }
 
-  boolean fieldAccessRequiresRewriting(DexEncodedField field, DexClass contextClass) {
+  boolean fieldAccessRequiresRewriting(DexEncodedField field, DexClassAndMethod context) {
     assert field != null;
     // Rewrite only when targeting other nest members private fields.
-    if (!field.accessFlags.isPrivate() || field.holder() == contextClass.type) {
+    if (!field.accessFlags.isPrivate() || field.holder() == context.getHolderType()) {
       return false;
     }
     DexClass fieldHolder = definitionFor(field.holder());
     assert fieldHolder != null; // from encodedField
-    return fieldHolder.getNestHost() == contextClass.getNestHost();
+    return fieldHolder.getNestHost() == context.getHolder().getNestHost();
   }
 
   private boolean holderRequiresBridge(DexClass holder) {
@@ -300,14 +310,16 @@
     if (holderRequiresBridge(holder)) {
       return bridgeMethod;
     }
+    assert holder.isProgramClass();
     // The map is used to avoid creating multiple times the bridge
     // and remembers the bridges to add.
-    Map<DexField, DexEncodedMethod> fieldMap = isGet ? getFieldBridges : putFieldBridges;
+    Map<DexField, ProgramMethod> fieldMap = isGet ? getFieldBridges : putFieldBridges;
+    assert holder.isProgramClass();
     fieldMap.computeIfAbsent(
         field.field,
         k ->
             DexEncodedMethod.createFieldAccessorBridge(
-                new DexFieldWithAccess(field, isGet), holder, bridgeMethod));
+                new DexFieldWithAccess(field, isGet), holder.asProgramClass(), bridgeMethod));
     return bridgeMethod;
   }
 
@@ -327,26 +339,23 @@
     }
     // The map is used to avoid creating multiple times the bridge
     // and remembers the bridges to add.
+    assert holder.isProgramClass();
     bridges.computeIfAbsent(
         method.method,
         k ->
             method.isInstanceInitializer()
-                ? method.toInitializerForwardingBridge(holder, bridgeMethod)
-                : method.toStaticForwardingBridge(holder, computeMethodBridge(method)));
+                ? method.toInitializerForwardingBridge(holder.asProgramClass(), bridgeMethod)
+                : method.toStaticForwardingBridge(
+                    holder.asProgramClass(), computeMethodBridge(method)));
     return bridgeMethod;
   }
 
   protected class NestBasedAccessDesugaringUseRegistry extends UseRegistry {
 
-    private final DexClass currentClass;
-    private DexMethod context;
+    private final DexClassAndMethod context;
 
-    NestBasedAccessDesugaringUseRegistry(DexClass currentClass) {
+    NestBasedAccessDesugaringUseRegistry(DexClassAndMethod context) {
       super(appView.options().itemFactory);
-      this.currentClass = currentClass;
-    }
-
-    public void setContext(DexMethod context) {
       this.context = context;
     }
 
@@ -357,7 +366,7 @@
         return false;
       }
       DexEncodedMethod encodedMethod = definitionFor(method, context, invokeType);
-      if (encodedMethod != null && invokeRequiresRewriting(encodedMethod, currentClass)) {
+      if (encodedMethod != null && invokeRequiresRewriting(encodedMethod, context)) {
         ensureInvokeBridge(encodedMethod);
         return true;
       }
@@ -366,7 +375,7 @@
 
     private boolean registerFieldAccess(DexField field, boolean isGet) {
       DexEncodedField encodedField = definitionFor(field);
-      if (encodedField != null && fieldAccessRequiresRewriting(encodedField, currentClass)) {
+      if (encodedField != null && fieldAccessRequiresRewriting(encodedField, context)) {
         ensureFieldAccessBridge(encodedField, isGet);
         return true;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
index ca10fe6..b97888d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
@@ -7,10 +7,10 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -54,12 +54,11 @@
   }
 
   private <E> void addDeferredBridgesAndMapMethods(
-      Map<E, DexEncodedMethod> bridges, BiConsumer<E, DexMethod> lensInserter) {
-    for (Map.Entry<E, DexEncodedMethod> entry : bridges.entrySet()) {
-      DexClass holder = definitionFor(entry.getValue().holder());
-      assert holder != null && holder.isProgramClass();
-      holder.asProgramClass().addMethod(entry.getValue());
-      lensInserter.accept(entry.getKey(), entry.getValue().method);
+      Map<E, ProgramMethod> bridges, BiConsumer<E, DexMethod> lensInserter) {
+    for (Map.Entry<E, ProgramMethod> entry : bridges.entrySet()) {
+      ProgramMethod method = entry.getValue();
+      method.getHolder().addMethod(method.getDefinition());
+      lensInserter.accept(entry.getKey(), method.getReference());
     }
     bridges.clear();
   }
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 312ae5d..8d1124b 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
@@ -8,6 +8,7 @@
 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.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -31,6 +32,7 @@
 import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Sets;
 import java.util.Collection;
 import java.util.LinkedList;
@@ -52,14 +54,14 @@
   }
 
   private final AppView<AppInfoWithLiveness> appView;
-  private Set<DexEncodedMethod> revisitedMethods = null;
+  private ProgramMethodSet revisitedMethods = null;
   private Mode mode = Mode.COLLECT;
 
   public CallSiteOptimizationInfoPropagator(AppView<AppInfoWithLiveness> appView) {
     assert appView.enableWholeProgramOptimizations();
     this.appView = appView;
     if (Log.isLoggingEnabledFor(CallSiteOptimizationInfoPropagator.class)) {
-      revisitedMethods = Sets.newIdentityHashSet();
+      revisitedMethods = ProgramMethodSet.create();
     }
   }
 
@@ -67,9 +69,12 @@
     assert Log.ENABLED;
     if (revisitedMethods != null) {
       Log.info(getClass(), "# of methods to revisit: %s", revisitedMethods.size());
-      for (DexEncodedMethod m : revisitedMethods) {
-        Log.info(getClass(), "%s: %s",
-            m.toSourceString(), m.getCallSiteOptimizationInfo().toString());
+      for (ProgramMethod m : revisitedMethods) {
+        Log.info(
+            getClass(),
+            "%s: %s",
+            m.toSourceString(),
+            m.getDefinition().getCallSiteOptimizationInfo().toString());
       }
     }
   }
@@ -245,7 +250,7 @@
       if (abstractValue.isSingleValue()) {
         assert appView.options().enablePropagationOfConstantsAtCallSites;
         SingleValue singleValue = abstractValue.asSingleValue();
-        if (singleValue.isMaterializableInContext(appView, code.method().holder())) {
+        if (singleValue.isMaterializableInContext(appView, code.context())) {
           Instruction replacement =
               singleValue.createMaterializingInstruction(appView, code, instr);
           replacement.setPosition(instr.getPosition());
@@ -318,27 +323,29 @@
   }
 
   @Override
-  public Set<DexEncodedMethod> methodsToRevisit() {
+  public ProgramMethodSet methodsToRevisit() {
     mode = Mode.REVISIT;
-    Set<DexEncodedMethod> targetsToRevisit = Sets.newIdentityHashSet();
+    ProgramMethodSet targetsToRevisit = ProgramMethodSet.create();
     for (DexProgramClass clazz : appView.appInfo().classes()) {
-      for (DexEncodedMethod method : clazz.methods()) {
-        assert !method.isObsolete();
-        if (method.shouldNotHaveCode()
-            || !method.hasCode()
-            || method.getCode().isEmptyVoidMethod()) {
-          continue;
-        }
-        // TODO(b/139246447): Assert no BOTTOM left.
-        CallSiteOptimizationInfo callSiteOptimizationInfo = method.getCallSiteOptimizationInfo();
-        if (!callSiteOptimizationInfo.hasUsefulOptimizationInfo(appView, method)) {
-          continue;
-        }
-        targetsToRevisit.add(method);
-        if (appView.options().testing.callSiteOptimizationInfoInspector != null) {
-          appView.options().testing.callSiteOptimizationInfoInspector.accept(method);
-        }
-      }
+      clazz.forEachProgramMethodMatching(
+          definition -> {
+            assert !definition.isObsolete();
+            if (definition.shouldNotHaveCode()
+                || !definition.hasCode()
+                || definition.getCode().isEmptyVoidMethod()) {
+              return false;
+            }
+            // TODO(b/139246447): Assert no BOTTOM left.
+            CallSiteOptimizationInfo callSiteOptimizationInfo =
+                definition.getCallSiteOptimizationInfo();
+            return callSiteOptimizationInfo.hasUsefulOptimizationInfo(appView, definition);
+          },
+          method -> {
+            targetsToRevisit.add(method);
+            if (appView.options().testing.callSiteOptimizationInfoInspector != null) {
+              appView.options().testing.callSiteOptimizationInfoInspector.accept(method);
+            }
+          });
     }
     if (revisitedMethods != null) {
       revisitedMethods.addAll(targetsToRevisit);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index db75756..e0066a7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.addMonitorEnterValue;
 import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.collectAllMonitorEnterValues;
 
@@ -16,6 +15,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
@@ -46,16 +46,13 @@
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Set;
-import java.util.function.Predicate;
 
 public final class DefaultInliningOracle implements InliningOracle, InliningStrategy {
 
   private final AppView<AppInfoWithLiveness> appView;
   private final Inliner inliner;
-  private final DexEncodedMethod method;
-  private final IRCode code;
+  private final ProgramMethod method;
   private final MethodProcessor methodProcessor;
-  private final Predicate<DexEncodedMethod> isProcessedConcurrently;
   private final InliningReasonStrategy reasonStrategy;
   private final int inliningInstructionLimit;
   private int instructionAllowance;
@@ -64,8 +61,7 @@
       AppView<AppInfoWithLiveness> appView,
       Inliner inliner,
       InliningReasonStrategy inliningReasonStrategy,
-      DexEncodedMethod method,
-      IRCode code,
+      ProgramMethod method,
       MethodProcessor methodProcessor,
       int inliningInstructionLimit,
       int inliningInstructionAllowance) {
@@ -73,9 +69,7 @@
     this.inliner = inliner;
     this.reasonStrategy = inliningReasonStrategy;
     this.method = method;
-    this.code = code;
     this.methodProcessor = methodProcessor;
-    this.isProcessedConcurrently = methodProcessor::isProcessedConcurrently;
     this.inliningInstructionLimit = inliningInstructionLimit;
     this.instructionAllowance = inliningInstructionAllowance;
   }
@@ -87,40 +81,29 @@
 
   private boolean isSingleTargetInvalid(
       InvokeMethod invoke,
-      DexEncodedMethod singleTarget,
+      ProgramMethod singleTarget,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     if (singleTarget == null) {
       throw new Unreachable(
           "Unexpected attempt to inline invoke that does not have a single target");
     }
 
-    if (singleTarget.isClassInitializer()) {
+    if (singleTarget.getDefinition().isClassInitializer()) {
       throw new Unreachable(
           "Unexpected attempt to invoke a class initializer (`"
-              + singleTarget.method.toSourceString()
+              + singleTarget.toSourceString()
               + "`)");
     }
 
-    if (!singleTarget.hasCode()) {
+    if (!singleTarget.getDefinition().hasCode()) {
       whyAreYouNotInliningReporter.reportInlineeDoesNotHaveCode();
       return true;
     }
 
-    DexClass clazz = appView.definitionFor(singleTarget.holder());
-    if (!clazz.isProgramClass()) {
-      if (clazz.isClasspathClass()) {
-        whyAreYouNotInliningReporter.reportClasspathMethod();
-      } else {
-        assert clazz.isLibraryClass();
-        whyAreYouNotInliningReporter.reportLibraryMethod();
-      }
-      return true;
-    }
-
     // Ignore the implicit receiver argument.
     int numberOfArguments =
         invoke.arguments().size() - BooleanUtils.intValue(invoke.isInvokeMethodWithReceiver());
-    int arity = singleTarget.method.getArity();
+    int arity = singleTarget.getReference().getArity();
     if (numberOfArguments != arity) {
       whyAreYouNotInliningReporter.reportIncorrectArity(numberOfArguments, arity);
       return true;
@@ -132,27 +115,28 @@
   @Override
   public boolean passesInliningConstraints(
       InvokeMethod invoke,
-      DexEncodedMethod singleTarget,
+      ProgramMethod singleTarget,
       Reason reason,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
-    if (singleTarget.getOptimizationInfo().neverInline()) {
+    DexEncodedMethod singleTargetMethod = singleTarget.getDefinition();
+    if (singleTargetMethod.getOptimizationInfo().neverInline()) {
       whyAreYouNotInliningReporter.reportMarkedAsNeverInline();
       return false;
     }
 
     // We don't inline into constructors when producing class files since this can mess up
     // the stackmap, see b/136250031
-    if (method.isInstanceInitializer()
+    if (method.getDefinition().isInstanceInitializer()
         && appView.options().isGeneratingClassFiles()
         && reason != Reason.FORCE) {
       whyAreYouNotInliningReporter.reportNoInliningIntoConstructorsWhenGeneratingClassFiles();
       return false;
     }
 
-    if (method == singleTarget) {
+    if (method.getDefinition() == singleTargetMethod) {
       // Cannot handle recursive inlining at this point.
       // Force inlined method should never be recursive.
-      assert !singleTarget.getOptimizationInfo().forceInline();
+      assert !singleTargetMethod.getOptimizationInfo().forceInline();
       whyAreYouNotInliningReporter.reportRecursiveMethod();
       return false;
     }
@@ -162,7 +146,7 @@
     // or optimized code. Right now this happens for the class class staticizer, as it just
     // processes all relevant methods in parallel with the full optimization pipeline enabled.
     // TODO(sgjesse): Add this assert "assert !isProcessedConcurrently.test(candidate);"
-    if (reason != Reason.FORCE && isProcessedConcurrently.test(singleTarget)) {
+    if (reason != Reason.FORCE && methodProcessor.isProcessedConcurrently(singleTarget)) {
       whyAreYouNotInliningReporter.reportProcessedConcurrently();
       return false;
     }
@@ -170,10 +154,9 @@
     InternalOptions options = appView.options();
     if (options.featureSplitConfiguration != null
         && !options.featureSplitConfiguration.inSameFeatureOrBase(
-            singleTarget.method, method.method)) {
+            singleTarget.getReference(), method.getReference())) {
       // Still allow inlining if we inline from the base into a feature.
-      DexClass clazz = asProgramClassOrNull(appView.definitionFor(singleTarget.method.holder));
-      if (!options.featureSplitConfiguration.isInBase(clazz.asProgramClass())) {
+      if (!options.featureSplitConfiguration.isInBase(singleTarget.getHolder())) {
         whyAreYouNotInliningReporter.reportInliningAcrossFeatureSplit();
         return false;
       }
@@ -216,16 +199,16 @@
     // Don't inline code with references beyond root main dex classes into a root main dex class.
     // If we do this it can increase the size of the main dex dependent classes.
     if (reason != Reason.FORCE
-        && inlineeRefersToClassesNotInMainDex(method.holder(), singleTarget)) {
+        && inlineeRefersToClassesNotInMainDex(method.getHolderType(), singleTarget)) {
       whyAreYouNotInliningReporter.reportInlineeRefersToClassesNotInMainDex();
       return false;
     }
     assert reason != Reason.FORCE
-        || !inlineeRefersToClassesNotInMainDex(method.holder(), singleTarget);
+        || !inlineeRefersToClassesNotInMainDex(method.getHolderType(), singleTarget);
     return true;
   }
 
-  private boolean inlineeRefersToClassesNotInMainDex(DexType holder, DexEncodedMethod target) {
+  private boolean inlineeRefersToClassesNotInMainDex(DexType holder, ProgramMethod target) {
     if (inliner.mainDexClasses.isEmpty() || !inliner.mainDexClasses.getRoots().contains(holder)) {
       return false;
     }
@@ -234,9 +217,9 @@
   }
 
   private boolean satisfiesRequirementsForSimpleInlining(
-      InvokeMethod invoke, DexEncodedMethod target) {
+      InvokeMethod invoke, ProgramMethod target) {
     // If we are looking for a simple method, only inline if actually simple.
-    Code code = target.getCode();
+    Code code = target.getDefinition().getCode();
     int instructionLimit = computeInstructionLimit(invoke, target);
     if (code.estimatedSizeForInliningAtMost(instructionLimit)) {
       return true;
@@ -244,9 +227,9 @@
     return false;
   }
 
-  private int computeInstructionLimit(InvokeMethod invoke, DexEncodedMethod candidate) {
+  private int computeInstructionLimit(InvokeMethod invoke, ProgramMethod candidate) {
     int instructionLimit = inliningInstructionLimit;
-    BitSet hints = candidate.getOptimizationInfo().getNonNullParamOrThrow();
+    BitSet hints = candidate.getDefinition().getOptimizationInfo().getNonNullParamOrThrow();
     if (hints != null) {
       List<Value> arguments = invoke.inValues();
       if (invoke.isInvokeMethodWithReceiver()) {
@@ -266,15 +249,15 @@
   }
 
   @Override
-  public DexEncodedMethod lookupSingleTarget(InvokeMethod invoke, DexType context) {
-    return invoke.lookupSingleTarget(appView, context);
+  public ProgramMethod lookupSingleTarget(InvokeMethod invoke, ProgramMethod context) {
+    return invoke.lookupSingleProgramTarget(appView, context);
   }
 
   @Override
   public InlineAction computeInlining(
       InvokeMethod invoke,
-      DexEncodedMethod singleTarget,
-      DexEncodedMethod context,
+      ProgramMethod singleTarget,
+      ProgramMethod context,
       ClassInitializationAnalysis classInitializationAnalysis,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     if (isSingleTargetInvalid(invoke, singleTarget, whyAreYouNotInliningReporter)) {
@@ -290,8 +273,9 @@
       return null;
     }
 
-    if (!singleTarget.isInliningCandidate(
-        method, reason, appView.appInfo(), whyAreYouNotInliningReporter)) {
+    if (!singleTarget
+        .getDefinition()
+        .isInliningCandidate(method, reason, appView.appInfo(), whyAreYouNotInliningReporter)) {
       return null;
     }
 
@@ -305,7 +289,7 @@
 
   public InlineAction computeForInvokeWithReceiver(
       InvokeMethodWithReceiver invoke,
-      DexEncodedMethod singleTarget,
+      ProgramMethod singleTarget,
       Reason reason,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     Value receiver = invoke.getReceiver();
@@ -322,7 +306,10 @@
       // receiver. Therefore, if the receiver may be null and the candidate inlinee does not
       // throw if the receiver is null before any other side effect, then we must synthesize a
       // null check.
-      if (!singleTarget.getOptimizationInfo().checksNullReceiverBeforeAnySideEffect()) {
+      if (!singleTarget
+          .getDefinition()
+          .getOptimizationInfo()
+          .checksNullReceiverBeforeAnySideEffect()) {
         InternalOptions options = appView.options();
         if (!options.enableInliningOfInvokesWithNullableReceivers) {
           whyAreYouNotInliningReporter.reportReceiverMaybeNull();
@@ -336,12 +323,16 @@
 
   public InlineAction computeForInvokeStatic(
       InvokeStatic invoke,
-      DexEncodedMethod singleTarget,
+      ProgramMethod singleTarget,
       Reason reason,
       ClassInitializationAnalysis classInitializationAnalysis,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     InlineAction action = new InlineAction(singleTarget, invoke, reason);
-    if (isTargetClassInitialized(invoke, method, singleTarget, classInitializationAnalysis)) {
+    if (isTargetClassInitialized(
+        invoke,
+        method.getDefinition(),
+        singleTarget.getDefinition(),
+        classInitializationAnalysis)) {
       return action;
     }
     if (appView.canUseInitClass()
@@ -409,8 +400,8 @@
 
   @Override
   public void ensureMethodProcessed(
-      DexEncodedMethod target, IRCode inlinee, OptimizationFeedback feedback) {
-    if (!target.isProcessed()) {
+      ProgramMethod target, IRCode inlinee, OptimizationFeedback feedback) {
+    if (!target.getDefinition().isProcessed()) {
       if (Log.ENABLED) {
         Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString());
       }
@@ -450,10 +441,11 @@
 
     // Allow inlining a constructor into a constructor of the same class, as the constructor code
     // is expected to adhere to the VM specification.
-    DexType callerMethodHolder = method.holder();
+    DexType callerMethodHolder = method.getHolderType();
     DexType calleeMethodHolder = inlinee.method().holder();
     // Calling a constructor on the same class from a constructor can always be inlined.
-    if (method.isInstanceInitializer() && callerMethodHolder == calleeMethodHolder) {
+    if (method.getDefinition().isInstanceInitializer()
+        && callerMethodHolder == calleeMethodHolder) {
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index 26c28d7..a48dece 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -5,8 +5,8 @@
 package com.android.tools.r8.ir.optimize;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
@@ -22,12 +22,12 @@
 final class ForcedInliningOracle implements InliningOracle, InliningStrategy {
 
   private final AppView<AppInfoWithLiveness> appView;
-  private final DexEncodedMethod method;
+  private final ProgramMethod method;
   private final Map<? extends InvokeMethod, Inliner.InliningInfo> invokesToInline;
 
   ForcedInliningOracle(
       AppView<AppInfoWithLiveness> appView,
-      DexEncodedMethod method,
+      ProgramMethod method,
       Map<? extends InvokeMethod, Inliner.InliningInfo> invokesToInline) {
     this.appView = appView;
     this.method = method;
@@ -42,26 +42,26 @@
   @Override
   public boolean passesInliningConstraints(
       InvokeMethod invoke,
-      DexEncodedMethod candidate,
+      ProgramMethod candidate,
       Reason reason,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     return true;
   }
 
   @Override
-  public DexEncodedMethod lookupSingleTarget(InvokeMethod invoke, DexType context) {
+  public ProgramMethod lookupSingleTarget(InvokeMethod invoke, ProgramMethod context) {
     Inliner.InliningInfo info = invokesToInline.get(invoke);
     if (info != null) {
       return info.target;
     }
-    return invoke.lookupSingleTarget(appView, context);
+    return invoke.lookupSingleProgramTarget(appView, context);
   }
 
   @Override
   public InlineAction computeInlining(
       InvokeMethod invoke,
-      DexEncodedMethod singleTarget,
-      DexEncodedMethod context,
+      ProgramMethod singleTarget,
+      ProgramMethod context,
       ClassInitializationAnalysis classInitializationAnalysis,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     return computeForInvoke(invoke, whyAreYouNotInliningReporter);
@@ -74,11 +74,11 @@
       return null;
     }
 
-    assert method != info.target;
+    assert method.getDefinition() != info.target.getDefinition();
     // Even though call to Inliner::performForcedInlining is supposed to be controlled by
     // the caller, it's still suspicious if we want to force inline something that is marked
     // with neverInline() flag.
-    assert !info.target.getOptimizationInfo().neverInline();
+    assert !info.target.getDefinition().getOptimizationInfo().neverInline();
     assert passesInliningConstraints(
         invoke, info.target, Reason.FORCE, whyAreYouNotInliningReporter);
     return new InlineAction(info.target, invoke, Reason.FORCE);
@@ -86,7 +86,7 @@
 
   @Override
   public void ensureMethodProcessed(
-      DexEncodedMethod target, IRCode inlinee, OptimizationFeedback feedback) {
+      ProgramMethod target, IRCode inlinee, OptimizationFeedback feedback) {
     // Do nothing. If the method is not yet processed, we still should
     // be able to build IR for inlining, though.
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index fdb59ae..6db2341 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.proto.ProtoInliningReasonStrategy;
 import com.android.tools.r8.ir.analysis.type.Nullability;
@@ -62,6 +63,7 @@
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
@@ -69,7 +71,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Deque;
-import java.util.HashMap;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
@@ -86,9 +88,10 @@
 
   // State for inlining methods which are known to be called twice.
   private boolean applyDoubleInlining = false;
-  private final Set<DexEncodedMethod> doubleInlineCallers = Sets.newIdentityHashSet();
-  private final Set<DexEncodedMethod> doubleInlineSelectedTargets = Sets.newIdentityHashSet();
-  private final Map<DexEncodedMethod, DexEncodedMethod> doubleInlineeCandidates = new HashMap<>();
+  private final ProgramMethodSet doubleInlineCallers = ProgramMethodSet.create();
+  private final ProgramMethodSet doubleInlineSelectedTargets = ProgramMethodSet.create();
+  private final Map<DexEncodedMethod, ProgramMethod> doubleInlineeCandidates =
+      new IdentityHashMap<>();
 
   private final AvailableApiExceptions availableApiExceptions;
 
@@ -113,25 +116,25 @@
   }
 
   boolean isBlacklisted(
-      DexEncodedMethod encodedMethod, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
-    DexMethod method = encodedMethod.method;
-    if (encodedMethod.getOptimizationInfo().forceInline()
-        && appView.appInfo().neverInline.contains(method)) {
+      ProgramMethod method, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
+    DexMethod reference = method.getReference();
+    if (method.getDefinition().getOptimizationInfo().forceInline()
+        && appView.appInfo().neverInline.contains(reference)) {
       throw new Unreachable();
     }
 
-    if (appView.appInfo().isPinned(method)) {
+    if (appView.appInfo().isPinned(reference)) {
       whyAreYouNotInliningReporter.reportPinned();
       return true;
     }
 
-    if (blacklist.contains(appView.graphLense().getOriginalMethodSignature(method))
-        || TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(method, appView)) {
+    if (blacklist.contains(appView.graphLense().getOriginalMethodSignature(reference))
+        || TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(reference, appView)) {
       whyAreYouNotInliningReporter.reportBlacklisted();
       return true;
     }
 
-    if (appView.appInfo().neverInline.contains(method)) {
+    if (appView.appInfo().neverInline.contains(reference)) {
       whyAreYouNotInliningReporter.reportMarkedAsNeverInline();
       return true;
     }
@@ -153,7 +156,7 @@
     return result;
   }
 
-  public ConstraintWithTarget computeInliningConstraint(IRCode code, DexEncodedMethod method) {
+  public ConstraintWithTarget computeInliningConstraint(IRCode code, ProgramMethod method) {
     if (containsPotentialCatchHandlerVerificationError(code)) {
       return ConstraintWithTarget.NEVER;
     }
@@ -168,7 +171,7 @@
         new InliningConstraints(appView, GraphLense.getIdentityLense());
     for (Instruction instruction : code.instructions()) {
       ConstraintWithTarget state =
-          instructionAllowedForInlining(instruction, inliningConstraints, method.holder());
+          instructionAllowedForInlining(instruction, inliningConstraints, method.getHolderType());
       if (state == ConstraintWithTarget.NEVER) {
         result = state;
         break;
@@ -179,8 +182,8 @@
     return result;
   }
 
-  private boolean returnsIntAsBoolean(IRCode code, DexEncodedMethod method) {
-    DexType returnType = method.method.proto.returnType;
+  private boolean returnsIntAsBoolean(IRCode code, ProgramMethod method) {
+    DexType returnType = method.getDefinition().returnType();
     for (BasicBlock basicBlock : code.blocks) {
       InstructionIterator instructionIterator = basicBlock.iterator();
       while (instructionIterator.hasNext()) {
@@ -195,16 +198,17 @@
     return false;
   }
 
-  boolean hasInliningAccess(DexEncodedMethod method, DexEncodedMethod target) {
-    if (!isVisibleWithFlags(target.holder(), method.holder(), target.accessFlags)) {
+  boolean hasInliningAccess(ProgramMethod method, ProgramMethod target) {
+    if (!isVisibleWithFlags(
+        target.getHolderType(), method.getHolderType(), target.getDefinition().accessFlags)) {
       return false;
     }
     // The class needs also to be visible for us to have access.
-    DexClass targetClass = appView.definitionFor(target.holder());
-    return isVisibleWithFlags(target.holder(), method.holder(), targetClass.accessFlags);
+    return isVisibleWithFlags(
+        target.getHolderType(), method.getHolderType(), target.getHolder().accessFlags);
   }
 
-  private boolean isVisibleWithFlags(DexType target, DexType context, AccessFlags flags) {
+  private boolean isVisibleWithFlags(DexType target, DexType context, AccessFlags<?> flags) {
     if (flags.isPublic()) {
       return true;
     }
@@ -218,12 +222,12 @@
     return target.isSamePackage(context);
   }
 
-  public synchronized boolean isDoubleInlineSelectedTarget(DexEncodedMethod method) {
+  public synchronized boolean isDoubleInlineSelectedTarget(ProgramMethod method) {
     return doubleInlineSelectedTargets.contains(method);
   }
 
   synchronized boolean satisfiesRequirementsForDoubleInlining(
-      DexEncodedMethod method, DexEncodedMethod target) {
+      ProgramMethod method, ProgramMethod target) {
     if (applyDoubleInlining) {
       // Don't perform the actual inlining if this was not selected.
       return doubleInlineSelectedTargets.contains(target);
@@ -234,25 +238,25 @@
     return false;
   }
 
-  synchronized void recordDoubleInliningCandidate(
-      DexEncodedMethod method, DexEncodedMethod target) {
+  synchronized void recordDoubleInliningCandidate(ProgramMethod method, ProgramMethod target) {
     if (applyDoubleInlining) {
       return;
     }
 
-    if (doubleInlineeCandidates.containsKey(target)) {
+    if (doubleInlineeCandidates.containsKey(target.getDefinition())) {
       // Both calls can be inlined.
-      doubleInlineCallers.add(doubleInlineeCandidates.get(target));
+      ProgramMethod doubleInlineeCandidate = doubleInlineeCandidates.get(target.getDefinition());
+      doubleInlineCallers.add(doubleInlineeCandidate);
       doubleInlineCallers.add(method);
       doubleInlineSelectedTargets.add(target);
     } else {
       // First call can be inlined.
-      doubleInlineeCandidates.put(target, method);
+      doubleInlineeCandidates.put(target.getDefinition(), method);
     }
   }
 
   @Override
-  public Set<DexEncodedMethod> methodsToRevisit() {
+  public ProgramMethodSet methodsToRevisit() {
     applyDoubleInlining = true;
     return doubleInlineCallers;
   }
@@ -574,14 +578,14 @@
 
   public static class InlineAction {
 
-    public final DexEncodedMethod target;
+    public final ProgramMethod target;
     public final Invoke invoke;
     final Reason reason;
 
     private boolean shouldSynthesizeInitClass;
     private boolean shouldSynthesizeNullCheckForReceiver;
 
-    InlineAction(DexEncodedMethod target, Invoke invoke, Reason reason) {
+    InlineAction(ProgramMethod target, Invoke invoke, Reason reason) {
       this.target = target;
       this.invoke = invoke;
       this.reason = reason;
@@ -600,7 +604,7 @@
     InlineeWithReason buildInliningIR(
         AppView<? extends AppInfoWithClassHierarchy> appView,
         InvokeMethod invoke,
-        DexEncodedMethod context,
+        ProgramMethod context,
         InliningIRProvider inliningIRProvider,
         LambdaMerger lambdaMerger,
         LensCodeRewriter lensCodeRewriter) {
@@ -624,9 +628,9 @@
       // building, and therefore, we do not need to do anything here. Upon writing, we will use the
       // flag "declared synchronized" instead of "synchronized".
       boolean shouldSynthesizeMonitorEnterExit =
-          target.accessFlags.isSynchronized() && options.isGeneratingClassFiles();
+          target.getDefinition().isSynchronized() && options.isGeneratingClassFiles();
       boolean isSynthesizingNullCheckForReceiverUsingMonitorEnter =
-          shouldSynthesizeMonitorEnterExit && !target.isStatic();
+          shouldSynthesizeMonitorEnterExit && !target.getDefinition().isStatic();
       if (shouldSynthesizeNullCheckForReceiver
           && !isSynthesizingNullCheckForReceiverUsingMonitorEnter) {
         synthesizeNullCheckForReceiver(appView, code);
@@ -709,11 +713,11 @@
         // If this is a static method, then the class object will act as the lock, so we load it
         // using a const-class instruction.
         Value lockValue;
-        if (target.isStatic()) {
+        if (target.getDefinition().isStatic()) {
           lockValue =
               code.createValue(
                   TypeElement.fromDexType(dexItemFactory.objectType, definitelyNotNull(), appView));
-          monitorEnterBlockIterator.add(new ConstClass(lockValue, target.holder()));
+          monitorEnterBlockIterator.add(new ConstClass(lockValue, target.getHolderType()));
         } else {
           lockValue = entryBlock.getInstructions().getFirst().asArgument().outValue();
         }
@@ -739,7 +743,7 @@
         }
       }
 
-      if (inliningIRProvider.shouldApplyCodeRewritings(code.method())) {
+      if (inliningIRProvider.shouldApplyCodeRewritings(target)) {
         assert lensCodeRewriter != null;
         lensCodeRewriter.rewrite(code, target);
       }
@@ -762,7 +766,7 @@
 
       InstructionListIterator iterator = initClassBlock.listIterator(code);
       iterator.setInsertionPosition(entryBlock.exit().getPosition());
-      iterator.add(new InitClass(code.createValue(TypeElement.getInt()), target.holder()));
+      iterator.add(new InitClass(code.createValue(TypeElement.getInt()), target.getHolderType()));
     }
 
     private void synthesizeNullCheckForReceiver(AppView<?> appView, IRCode code) {
@@ -842,17 +846,17 @@
   }
 
   public static class InliningInfo {
-    public final DexEncodedMethod target;
+    public final ProgramMethod target;
     public final DexType receiverType; // null, if unknown
 
-    public InliningInfo(DexEncodedMethod target, DexType receiverType) {
+    public InliningInfo(ProgramMethod target, DexType receiverType) {
       this.target = target;
       this.receiverType = receiverType;
     }
   }
 
   public void performForcedInlining(
-      DexEncodedMethod method,
+      ProgramMethod method,
       IRCode code,
       Map<? extends InvokeMethod, InliningInfo> invokesToInline,
       InliningIRProvider inliningIRProvider) {
@@ -860,8 +864,9 @@
     performInliningImpl(
         oracle, oracle, method, code, OptimizationFeedbackIgnore.getInstance(), inliningIRProvider);
   }
+
   public void performInlining(
-      DexEncodedMethod method,
+      ProgramMethod method,
       IRCode code,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor) {
@@ -874,7 +879,7 @@
   }
 
   public void performInlining(
-      DexEncodedMethod method,
+      ProgramMethod method,
       IRCode code,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
@@ -883,7 +888,6 @@
     DefaultInliningOracle oracle =
         createDefaultOracle(
             method,
-            code,
             methodProcessor,
             options.inliningInstructionLimit,
             options.inliningInstructionAllowance - numberOfInstructions(code),
@@ -904,14 +908,12 @@
   }
 
   public DefaultInliningOracle createDefaultOracle(
-      DexEncodedMethod method,
-      IRCode code,
+      ProgramMethod method,
       MethodProcessor methodProcessor,
       int inliningInstructionLimit,
       int inliningInstructionAllowance) {
     return createDefaultOracle(
         method,
-        code,
         methodProcessor,
         inliningInstructionLimit,
         inliningInstructionAllowance,
@@ -919,8 +921,7 @@
   }
 
   public DefaultInliningOracle createDefaultOracle(
-      DexEncodedMethod method,
-      IRCode code,
+      ProgramMethod method,
       MethodProcessor methodProcessor,
       int inliningInstructionLimit,
       int inliningInstructionAllowance,
@@ -930,7 +931,6 @@
         this,
         inliningReasonStrategy,
         method,
-        code,
         methodProcessor,
         inliningInstructionLimit,
         inliningInstructionAllowance);
@@ -939,7 +939,7 @@
   private void performInliningImpl(
       InliningStrategy strategy,
       InliningOracle oracle,
-      DexEncodedMethod context,
+      ProgramMethod context,
       IRCode code,
       OptimizationFeedback feedback,
       InliningIRProvider inliningIRProvider) {
@@ -964,16 +964,19 @@
         if (current.isInvokeMethod()) {
           InvokeMethod invoke = current.asInvokeMethod();
           // TODO(b/142116551): This should be equivalent to invoke.lookupSingleTarget()!
-          DexEncodedMethod singleTarget = oracle.lookupSingleTarget(invoke, context.holder());
+          ProgramMethod singleTarget = oracle.lookupSingleTarget(invoke, context);
           if (singleTarget == null) {
-            WhyAreYouNotInliningReporter.handleInvokeWithUnknownTarget(invoke, appView, context);
+            WhyAreYouNotInliningReporter.handleInvokeWithUnknownTarget(
+                invoke, appView, context.getDefinition());
             continue;
           }
 
+          DexEncodedMethod singleTargetMethod = singleTarget.getDefinition();
           WhyAreYouNotInliningReporter whyAreYouNotInliningReporter =
               oracle.isForcedInliningOracle()
                   ? NopWhyAreYouNotInliningReporter.getInstance()
-                  : WhyAreYouNotInliningReporter.createFor(singleTarget, appView, context);
+                  : WhyAreYouNotInliningReporter.createFor(
+                      singleTargetMethod, appView, context.getDefinition());
           InlineAction action =
               oracle.computeInlining(
                   invoke,
@@ -1012,8 +1015,8 @@
           strategy.ensureMethodProcessed(singleTarget, inlinee.code, feedback);
 
           // Make sure constructor inlining is legal.
-          assert !singleTarget.isClassInitializer();
-          if (singleTarget.isInstanceInitializer()
+          assert !singleTargetMethod.isClassInitializer();
+          if (singleTargetMethod.isInstanceInitializer()
               && !strategy.canInlineInstanceInitializer(
                   inlinee.code, whyAreYouNotInliningReporter)) {
             assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
@@ -1041,22 +1044,22 @@
               getDowncastTypeIfNeeded(strategy, invoke, singleTarget));
 
           if (inlinee.reason == Reason.SINGLE_CALLER) {
-            feedback.markInlinedIntoSingleCallSite(singleTarget);
+            feedback.markInlinedIntoSingleCallSite(singleTargetMethod);
           }
 
           classInitializationAnalysis.notifyCodeHasChanged();
           postProcessInlineeBlocks(code, inlinee.code, blockIterator, block);
 
           // The synthetic and bridge flags are maintained only if the inlinee has also these flags.
-          if (context.accessFlags.isBridge() && !inlinee.code.method().accessFlags.isBridge()) {
-            context.accessFlags.demoteFromBridge();
+          if (context.getDefinition().isBridge() && !inlinee.code.method().accessFlags.isBridge()) {
+            context.getDefinition().accessFlags.demoteFromBridge();
           }
-          if (context.accessFlags.isSynthetic()
+          if (context.getDefinition().accessFlags.isSynthetic()
               && !inlinee.code.method().accessFlags.isSynthetic()) {
-            context.accessFlags.demoteFromSynthetic();
+            context.getDefinition().accessFlags.demoteFromSynthetic();
           }
 
-          context.copyMetadata(singleTarget);
+          context.getDefinition().copyMetadata(singleTargetMethod);
 
           if (inlineeMayHaveInvokeMethod && options.applyInliningToInlinee) {
             if (inlineeStack.size() + 1 > options.applyInliningToInlineeMaxDepth
@@ -1103,7 +1106,7 @@
   }
 
   private DexType getDowncastTypeIfNeeded(
-      InliningStrategy strategy, InvokeMethod invoke, DexEncodedMethod target) {
+      InliningStrategy strategy, InvokeMethod invoke, ProgramMethod target) {
     if (invoke.isInvokeMethodWithReceiver()) {
       // If the invoke has a receiver but the actual type of the receiver is different
       // from the computed target holder, inlining requires a downcast of the receiver.
@@ -1113,8 +1116,8 @@
         // method holder as a fallback.
         receiverType = invoke.getInvokedMethod().holder;
       }
-      if (!appView.appInfo().isSubtype(receiverType, target.holder())) {
-        return target.holder();
+      if (!appView.appInfo().isSubtype(receiverType, target.getHolderType())) {
+        return target.getHolderType();
       }
     }
     return null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index 6f4570f..a9e4736 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -4,8 +4,7 @@
 
 package com.android.tools.r8.ir.optimize;
 
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
@@ -20,18 +19,18 @@
   boolean isForcedInliningOracle();
 
   // TODO(b/142116551): This should be equivalent to invoke.lookupSingleTarget(appView, context)!
-  DexEncodedMethod lookupSingleTarget(InvokeMethod invoke, DexType context);
+  ProgramMethod lookupSingleTarget(InvokeMethod invoke, ProgramMethod context);
 
   boolean passesInliningConstraints(
       InvokeMethod invoke,
-      DexEncodedMethod candidate,
+      ProgramMethod candidate,
       Reason reason,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
 
   InlineAction computeInlining(
       InvokeMethod invoke,
-      DexEncodedMethod singleTarget,
-      DexEncodedMethod context,
+      ProgramMethod singleTarget,
+      ProgramMethod context,
       ClassInitializationAnalysis classInitializationAnalysis,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
index 0f22b14..cda0a6b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.ir.optimize;
 
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -43,8 +43,7 @@
   /** Inform the strategy that the inlinee has been inlined. */
   void markInlined(InlineeWithReason inlinee);
 
-  void ensureMethodProcessed(
-      DexEncodedMethod target, IRCode inlinee, OptimizationFeedback feedback);
+  void ensureMethodProcessed(ProgramMethod target, IRCode inlinee, OptimizationFeedback feedback);
 
   DexType getReceiverTypeIfKnown(InvokeMethod invoke);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index e6dcbbe..458f4b1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -225,7 +226,7 @@
 
   private void rewriteInvokeMethodWithConstantValues(
       IRCode code,
-      DexType context,
+      ProgramMethod context,
       Set<Value> affectedValues,
       ListIterator<BasicBlock> blocks,
       InstructionListIterator iterator,
@@ -235,7 +236,7 @@
     if (!invokedHolder.isClassType()) {
       return;
     }
-    DexEncodedMethod target = current.lookupSingleTarget(appView, context);
+    DexEncodedMethod target = current.lookupSingleTarget(appView, context.getHolderType());
     if (target != null && target.isInstanceInitializer()) {
       // Member value propagation does not apply to constructors. Removing a call to a constructor
       // that is marked as having no side effects could lead to verification errors, due to
@@ -302,10 +303,10 @@
         current.setOutValue(null);
 
         if (current.isInvokeMethodWithReceiver()) {
-          replaceInstructionByNullCheckIfPossible(current, iterator, context);
+          replaceInstructionByNullCheckIfPossible(current, iterator, context.getHolderType());
         } else if (current.isInvokeStatic()) {
           replaceInstructionByInitClassIfPossible(
-              current, target.holder(), code, iterator, context);
+              current, target.holder(), code, iterator, context.getHolderType());
         }
 
         // Insert the definition of the replacement.
@@ -390,7 +391,7 @@
           && singleValue.asSingleFieldValue().getField() == field) {
         return;
       }
-      if (singleValue.isMaterializableInContext(appView, code.method().holder())) {
+      if (singleValue.isMaterializableInContext(appView, code.context())) {
         BasicBlock block = current.getBlock();
         DexType context = code.method().holder();
         Position position = current.getPosition();
@@ -534,7 +535,7 @@
       ListIterator<BasicBlock> blockIterator,
       Set<Value> affectedValues,
       Predicate<BasicBlock> blockTester) {
-    DexType context = code.method().holder();
+    ProgramMethod context = code.context();
     while (blockIterator.hasNext()) {
       BasicBlock block = blockIterator.next();
       if (!blockTester.test(block)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 6a05911..2b9456a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.optimize;
 
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 
@@ -12,10 +11,10 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.ClasspathMethod;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -29,6 +28,7 @@
 import com.android.tools.r8.graph.GraphLense;
 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.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -65,9 +65,11 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
-import com.google.common.collect.Sets;
+import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -77,7 +79,6 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
-import java.util.Set;
 import java.util.function.Consumer;
 
 /**
@@ -94,24 +95,25 @@
  *   <li>Second, {@link Outliner#selectMethodsForOutlining()} is called to retain the lists of
  *       methods found in the first step that are large enough (see {@link InternalOptions#outline}
  *       {@link OutlineOptions#threshold}), and the methods to be further analyzed for outlining is
- *       returned by {@link Outliner#getMethodsSelectedForOutlining}. Each selected method is then
+ *       returned by {@link Outliner#buildMethodsSelectedForOutlining}. Each selected method is then
  *       converted back to IR and passed to {@link Outliner#identifyOutlineSites(IRCode)}, which
  *       then stores concrete outlining candidates in {@link Outliner#outlineSites}.
  *   <li>Third, {@link Outliner#buildOutlinerClass(DexType)} is called to construct the <em>outline
  *       support class</em> containing a static helper method for each outline candidate that occurs
  *       frequently enough. Each selected method is then converted to IR, passed to {@link
- *       Outliner#applyOutliningCandidate(IRCode)} to perform the outlining, and
- *       converted back to the output format (DEX or CF).
+ *       Outliner#applyOutliningCandidate(IRCode)} to perform the outlining, and converted back to
+ *       the output format (DEX or CF).
  * </ul>
  */
 public class Outliner {
 
   /** Result of first step (see {@link Outliner#createOutlineMethodIdentifierGenerator()}. */
-  private final List<List<DexEncodedMethod>> candidateMethodLists = new ArrayList<>();
+  private final List<List<ProgramMethod>> candidateMethodLists = new ArrayList<>();
   /** Result of second step (see {@link Outliner#selectMethodsForOutlining()}. */
-  private final Set<DexEncodedMethod> methodsSelectedForOutlining = Sets.newIdentityHashSet();
+  private final LongLivedProgramMethodSetBuilder methodsSelectedForOutlining =
+      new LongLivedProgramMethodSetBuilder();
   /** Result of second step (see {@link Outliner#selectMethodsForOutlining()}. */
-  private final Map<Outline, List<DexEncodedMethod>> outlineSites = new HashMap<>();
+  private final Map<Outline, List<ProgramMethod>> outlineSites = new HashMap<>();
   /** Result of third step (see {@link Outliner#buildOutlinerClass(DexType)}. */
   private final Map<Outline, DexMethod> generatedOutlines = new HashMap<>();
 
@@ -716,7 +718,7 @@
   // replacing.
   abstract private class OutlineSpotter {
 
-    final DexEncodedMethod method;
+    final ProgramMethod method;
     final BasicBlock block;
     // instructionArrayCache is block.getInstructions() copied to an ArrayList.
     private List<Instruction> instructionArrayCache = null;
@@ -733,7 +735,7 @@
     int returnValueUsersLeft;
     int pendingNewInstanceIndex = -1;
 
-    OutlineSpotter(DexEncodedMethod method, BasicBlock block) {
+    OutlineSpotter(ProgramMethod method, BasicBlock block) {
       this.method = method;
       this.block = block;
       reset(0);
@@ -862,7 +864,7 @@
       // See whether we could move this invoke somewhere else. We reuse the logic from inlining
       // here, as the constraints are the same.
       ConstraintWithTarget constraint =
-          invoke.inliningConstraint(inliningConstraints, method.holder());
+          invoke.inliningConstraint(inliningConstraints, method.getHolderType());
       if (constraint != ConstraintWithTarget.ALWAYS) {
         return false;
       }
@@ -1133,12 +1135,10 @@
   // TODO(sgjesse): This does not take several usages in the same method into account.
   private class OutlineMethodIdentifier extends OutlineSpotter {
 
-    private final Map<Outline, List<DexEncodedMethod>> candidateMap;
+    private final Map<Outline, List<ProgramMethod>> candidateMap;
 
     OutlineMethodIdentifier(
-        DexEncodedMethod method,
-        BasicBlock block,
-        Map<Outline, List<DexEncodedMethod>> candidateMap) {
+        ProgramMethod method, BasicBlock block, Map<Outline, List<ProgramMethod>> candidateMap) {
       super(method, block);
       this.candidateMap = candidateMap;
     }
@@ -1150,8 +1150,8 @@
       }
     }
 
-    private List<DexEncodedMethod> addOutlineMethodList(Outline outline) {
-      List<DexEncodedMethod> result = new ArrayList<>();
+    private List<ProgramMethod> addOutlineMethodList(Outline outline) {
+      List<ProgramMethod> result = new ArrayList<>();
       candidateMethodLists.add(result);
       return result;
     }
@@ -1159,7 +1159,7 @@
 
   private class OutlineSiteIdentifier extends OutlineSpotter {
 
-    OutlineSiteIdentifier(DexEncodedMethod method, BasicBlock block) {
+    OutlineSiteIdentifier(ProgramMethod method, BasicBlock block) {
       super(method, block);
     }
 
@@ -1184,7 +1184,7 @@
         ListIterator<BasicBlock> blocksIterator,
         BasicBlock block,
         List<Integer> toRemove) {
-      super(code.method(), block);
+      super(code.context(), block);
       this.code = code;
       this.blocksIterator = blocksIterator;
       this.toRemove = toRemove;
@@ -1259,7 +1259,9 @@
     /** When assertions are enabled, remove method from the outline's list. */
     private boolean removeMethodFromOutlineList(Outline outline) {
       synchronized (outlineSites) {
-        assert outlineSites.get(outline).remove(method);
+        assert ListUtils.removeFirstMatch(
+            outlineSites.get(outline),
+            element -> element.getDefinition() == method.getDefinition());
       }
       return true;
     }
@@ -1275,14 +1277,14 @@
     // out-value of invokes to null), this map must not be used except for identifying methods
     // potentially relevant to outlining. OutlineMethodIdentifier will add method lists to
     // candidateMethodLists whenever it adds an entry to candidateMap.
-    Map<Outline, List<DexEncodedMethod>> candidateMap = new HashMap<>();
+    Map<Outline, List<ProgramMethod>> candidateMap = new HashMap<>();
     assert candidateMethodLists.isEmpty();
     assert outlineMethodIdentifierGenerator == null;
     outlineMethodIdentifierGenerator =
         code -> {
           assert !code.method().getCode().isOutlineCode();
           for (BasicBlock block : code.blocks) {
-            new OutlineMethodIdentifier(code.method(), block, candidateMap).process();
+            new OutlineMethodIdentifier(code.context(), block, candidateMap).process();
           }
         };
   }
@@ -1296,38 +1298,30 @@
 
   public void identifyOutlineSites(IRCode code) {
     assert !code.method().getCode().isOutlineCode();
-    DexClass clazz = asProgramClassOrNull(appView.definitionFor(code.method().holder()));
-    assert clazz != null;
-    if (clazz == null) {
-      return;
-    }
+    DexProgramClass clazz = code.context().getHolder();
     if (appView.options().featureSplitConfiguration != null
-        && appView.options().featureSplitConfiguration.isInFeature(clazz.asProgramClass())) {
+        && appView.options().featureSplitConfiguration.isInFeature(clazz)) {
       return;
     }
-
     for (BasicBlock block : code.blocks) {
-      new OutlineSiteIdentifier(code.method(), block).process();
+      new OutlineSiteIdentifier(code.context(), block).process();
     }
   }
 
   public boolean selectMethodsForOutlining() {
-    assert methodsSelectedForOutlining.size() == 0;
-    assert outlineSites.size() == 0;
-    for (List<DexEncodedMethod> outlineMethods : candidateMethodLists) {
+    assert methodsSelectedForOutlining.isEmpty();
+    assert outlineSites.isEmpty();
+    for (List<ProgramMethod> outlineMethods : candidateMethodLists) {
       if (outlineMethods.size() >= appView.options().outline.threshold) {
-        for (DexEncodedMethod outlineMethod : outlineMethods) {
-          methodsSelectedForOutlining.add(
-              appView.graphLense().mapDexEncodedMethod(outlineMethod, appView));
-        }
+        methodsSelectedForOutlining.addAll(outlineMethods);
       }
     }
     candidateMethodLists.clear();
-    return methodsSelectedForOutlining.size() > 0;
+    return !methodsSelectedForOutlining.isEmpty();
   }
 
-  public Set<DexEncodedMethod> getMethodsSelectedForOutlining() {
-    return methodsSelectedForOutlining;
+  public ProgramMethodSet buildMethodsSelectedForOutlining() {
+    return methodsSelectedForOutlining.build(appView);
   }
 
   public DexProgramClass buildOutlinerClass(DexType type) {
@@ -1345,7 +1339,7 @@
       DexString methodName =
           appView.dexItemFactory().createString(OutlineOptions.METHOD_PREFIX + count);
       DexMethod method = outline.buildMethod(type, methodName);
-      List<DexEncodedMethod> sites = outlineSites.get(outline);
+      List<ProgramMethod> sites = outlineSites.get(outline);
       assert !sites.isEmpty();
       direct[count] =
           new DexEncodedMethod(
@@ -1356,7 +1350,7 @@
               new OutlineCode(outline),
               true);
       if (appView.options().isGeneratingClassFiles()) {
-        direct[count].upgradeClassFileVersion(sites.get(0).getClassFileVersion());
+        direct[count].upgradeClassFileVersion(sites.get(0).getDefinition().getClassFileVersion());
       }
       generatedOutlines.put(outline, method);
       count++;
@@ -1393,10 +1387,10 @@
   }
 
   private List<Outline> selectOutlines() {
-    assert outlineSites.size() > 0;
+    assert !outlineSites.isEmpty();
     assert candidateMethodLists.isEmpty();
     List<Outline> result = new ArrayList<>();
-    for (Entry<Outline, List<DexEncodedMethod>> entry : outlineSites.entrySet()) {
+    for (Entry<Outline, List<ProgramMethod>> entry : outlineSites.entrySet()) {
       if (entry.getValue().size() >= appView.options().outline.threshold) {
         result.add(entry.getKey());
       }
@@ -1603,10 +1597,9 @@
     }
 
     @Override
-    public IRCode buildIR(DexEncodedMethod encodedMethod, AppView<?> appView, Origin origin) {
-      OutlineSourceCode source = new OutlineSourceCode(outline, encodedMethod.method);
-      return IRBuilder.create(encodedMethod, appView, source, origin)
-          .build(encodedMethod);
+    public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+      OutlineSourceCode source = new OutlineSourceCode(outline, method.getReference());
+      return IRBuilder.create(method, appView, source, origin).build(method);
     }
 
     @Override
@@ -1615,7 +1608,12 @@
     }
 
     @Override
-    public void registerCodeReferences(DexEncodedMethod method, UseRegistry registry) {
+    public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
+      throw new Unreachable();
+    }
+
+    @Override
+    public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) {
       throw new Unreachable();
     }
 
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 ccb328e..1ba460e 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
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
@@ -54,7 +55,7 @@
   private static final int MAX_CAPACITY_PER_BLOCK = 50;
 
   private final AppView<?> appView;
-  private final DexEncodedMethod method;
+  private final ProgramMethod method;
   private final IRCode code;
 
   // Values that may require type propagation.
@@ -69,7 +70,7 @@
 
   public RedundantFieldLoadElimination(AppView<?> appView, IRCode code) {
     this.appView = appView;
-    this.method = code.method();
+    this.method = code.context();
     this.code = code;
   }
 
@@ -105,7 +106,7 @@
     private final SingleValue value;
 
     private MaterializableValue(SingleValue value) {
-      assert value.isMaterializableInContext(appView.withLiveness(), method.holder());
+      assert value.isMaterializableInContext(appView.withLiveness(), method);
       this.value = value;
     }
 
@@ -153,14 +154,14 @@
     if (appView.enableWholeProgramOptimizations()) {
       return appView.appInfo().resolveField(field);
     }
-    if (field.holder == method.holder()) {
+    if (field.holder == method.getHolderType()) {
       return appView.definitionFor(field);
     }
     return null;
   }
 
   public void run() {
-    DexType context = method.holder();
+    DexType context = method.getHolderType();
     Reference2IntMap<BasicBlock> pendingNormalSuccessors = new Reference2IntOpenHashMap<>();
     for (BasicBlock block : code.blocks) {
       if (!block.hasUniqueNormalSuccessor()) {
@@ -213,7 +214,8 @@
               FieldAndObject fieldAndObject = new FieldAndObject(field, object);
               ExistingValue value = new ExistingValue(instancePut.value());
               if (isFinal(definition)) {
-                assert method.isInstanceInitializer() || verifyWasInstanceInitializer();
+                assert method.getDefinition().isInstanceInitializer()
+                    || verifyWasInstanceInitializer();
                 activeState.putFinalInstanceField(fieldAndObject, value);
               } else {
                 activeState.putNonFinalInstanceField(fieldAndObject, value);
@@ -244,7 +246,7 @@
               killNonFinalActiveFields(staticPut);
               ExistingValue value = new ExistingValue(staticPut.value());
               if (definition.isFinal()) {
-                assert method.isClassInitializer();
+                assert method.getDefinition().isClassInitializer();
                 activeState.putFinalStaticField(field, value);
               } else {
                 activeState.putNonFinalStaticField(field, value);
@@ -332,11 +334,11 @@
   private boolean verifyWasInstanceInitializer() {
     VerticallyMergedClasses verticallyMergedClasses = appView.verticallyMergedClasses();
     assert verticallyMergedClasses != null;
-    assert verticallyMergedClasses.isTarget(method.holder());
+    assert verticallyMergedClasses.isTarget(method.getHolderType());
     assert appView
         .dexItemFactory()
-        .isConstructor(appView.graphLense().getOriginalMethodSignature(method.method));
-    assert method.getOptimizationInfo().forceInline();
+        .isConstructor(appView.graphLense().getOriginalMethodSignature(method.getReference()));
+    assert method.getDefinition().getOptimizationInfo().forceInline();
     return true;
   }
 
@@ -346,7 +348,7 @@
       return;
     }
 
-    DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.holder());
+    DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.getHolderType());
     if (singleTarget == null || !singleTarget.isInstanceInitializer()) {
       killAllNonFinalActiveFields();
       return;
@@ -374,7 +376,7 @@
             activeState.putNonFinalInstanceField(fieldAndObject, new ExistingValue(value));
           } else if (info.isSingleValue()) {
             SingleValue value = info.asSingleValue();
-            if (value.isMaterializableInContext(appView.withLiveness(), method.holder())) {
+            if (value.isMaterializableInContext(appView.withLiveness(), method)) {
               Value object = invoke.getReceiver().getAliasedValue();
               FieldAndObject fieldAndObject = new FieldAndObject(field.field, object);
               activeState.putNonFinalInstanceField(fieldAndObject, new MaterializableValue(value));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index c60da19..1f410ef 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -56,7 +56,7 @@
         }
         DexType type = null;
         if (current.isInvokeVirtual()) {
-          type = getTypeForGetClass( appView, context, current.asInvokeVirtual());
+          type = getTypeForGetClass(appView, context, current.asInvokeVirtual());
         } else if (current.isInvokeStatic()) {
           type = getTypeForClassForName(
               appView, classInitializationAnalysis, context, current.asInvokeStatic());
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 d5d51e1..7bec65e 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
@@ -94,7 +94,7 @@
     List<DexEncodedField> switchMapFields = clazz.staticFields().stream()
         .filter(this::maybeIsSwitchMap).collect(Collectors.toList());
     if (!switchMapFields.isEmpty()) {
-      IRCode initializer = clazz.getClassInitializer().buildIR(appView, clazz.origin);
+      IRCode initializer = clazz.getProgramClassInitializer().buildIR(appView);
       switchMapFields.forEach(field -> extractSwitchMap(field, initializer));
     }
   }
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 798b820..e7104f3 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
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -166,7 +167,7 @@
       CodeRewriter codeRewriter,
       StringOptimizer stringOptimizer,
       EnumValueOptimizer enumValueOptimizer,
-      DexEncodedMethod method,
+      ProgramMethod method,
       IRCode code,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
@@ -297,7 +298,8 @@
       codeRewriter.simplifyControlFlow(code);
       // If a method was inlined we may see more trivial computation/conversion of String.
       boolean isDebugMode =
-          appView.options().debug || method.getOptimizationInfo().isReachabilitySensitive();
+          appView.options().debug
+              || method.getDefinition().getOptimizationInfo().isReachabilitySensitive();
       if (!isDebugMode) {
         // Reflection/string optimization 3. trivial conversion/computation on const-string
         stringOptimizer.computeTrivialOperationsOnConstString(code);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
index da76b7b..3feac87 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
@@ -10,8 +10,8 @@
 import static com.android.tools.r8.ir.code.Opcodes.RETURN;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -44,19 +44,19 @@
   boolean willExceedInstructionBudget(
       IRCode code,
       DexProgramClass eligibleClass,
-      Map<InvokeMethod, DexEncodedMethod> directInlinees,
-      List<DexEncodedMethod> indirectInlinees) {
+      Map<InvokeMethod, ProgramMethod> directInlinees,
+      List<ProgramMethod> indirectInlinees) {
     if (appView.appInfo().alwaysClassInline.contains(eligibleClass.type)) {
       return false;
     }
 
-    for (DexEncodedMethod inlinee : indirectInlinees) {
+    for (ProgramMethod inlinee : indirectInlinees) {
       // We do not have the corresponding invoke instruction for the inlinees that are not called
       // directly from `code` (these are called indirectly from one of the methods in
       // `directInlinees`). Therefore, we currently choose not to build IR for estimating the number
       // of non-materializing instructions, since we cannot cache the IR (it would have the wrong
       // position).
-      int increment = inlinee.getCode().estimatedSizeForInlining();
+      int increment = inlinee.getDefinition().getCode().estimatedSizeForInlining();
       if (exceedsInstructionBudgetAfterIncrement(increment)) {
         return true;
       }
@@ -67,14 +67,14 @@
     int numberOfSeenDirectInlinees = 0;
     int numberOfDirectInlinees = directInlinees.size();
     for (InvokeMethod invoke : code.<InvokeMethod>instructions(Instruction::isInvokeMethod)) {
-      DexEncodedMethod inlinee = directInlinees.get(invoke);
+      ProgramMethod inlinee = directInlinees.get(invoke);
       if (inlinee == null) {
         // Not a direct inlinee.
         continue;
       }
       IRCode inliningIR = inliningIRProvider.getAndCacheInliningIR(invoke, inlinee);
       int increment =
-          inlinee.getCode().estimatedSizeForInlining()
+          inlinee.getDefinition().getCode().estimatedSizeForInlining()
               - estimateNumberOfNonMaterializingInstructions(invoke, inliningIR);
       assert increment >= 0;
       if (exceedsInstructionBudgetAfterIncrement(increment)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 9a76a99..59e9a94 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.classinliner;
 
+import static com.android.tools.r8.graph.DexEncodedMethod.asProgramMethodOrNull;
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.google.common.base.Predicates.alwaysFalse;
 
@@ -16,7 +17,7 @@
 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.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -57,6 +58,7 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
@@ -89,7 +91,7 @@
   private final Inliner inliner;
   private final Function<DexClass, EligibilityStatus> isClassEligible;
   private final MethodProcessor methodProcessor;
-  private final DexEncodedMethod method;
+  private final ProgramMethod method;
   private final Instruction root;
 
   private Value eligibleInstance;
@@ -98,14 +100,14 @@
   private final Map<InvokeMethodWithReceiver, InliningInfo> methodCallsOnInstance =
       new IdentityHashMap<>();
 
-  private final Set<DexEncodedMethod> indirectMethodCallsOnInstance = Sets.newIdentityHashSet();
+  private final ProgramMethodSet indirectMethodCallsOnInstance = ProgramMethodSet.create();
   private final Map<InvokeMethod, InliningInfo> extraMethodCalls
       = new IdentityHashMap<>();
   private final List<Pair<InvokeMethod, Integer>> unusedArguments
       = new ArrayList<>();
 
-  private final Map<InvokeMethod, DexEncodedMethod> directInlinees = new IdentityHashMap<>();
-  private final List<DexEncodedMethod> indirectInlinees = new ArrayList<>();
+  private final Map<InvokeMethod, ProgramMethod> directInlinees = new IdentityHashMap<>();
+  private final List<ProgramMethod> indirectInlinees = new ArrayList<>();
 
   // Sets of values that must/may be an alias of the "root" instance (including the root instance
   // itself).
@@ -116,7 +118,7 @@
       Inliner inliner,
       Function<DexClass, EligibilityStatus> isClassEligible,
       MethodProcessor methodProcessor,
-      DexEncodedMethod method,
+      ProgramMethod method,
       Instruction root) {
     this.appView = appView;
     this.dexItemFactory = appView.dexItemFactory();
@@ -132,11 +134,11 @@
     return eligibleClass;
   }
 
-  Map<InvokeMethod, DexEncodedMethod> getDirectInlinees() {
+  Map<InvokeMethod, ProgramMethod> getDirectInlinees() {
     return directInlinees;
   }
 
-  List<DexEncodedMethod> getIndirectInlinees() {
+  List<ProgramMethod> getIndirectInlinees() {
     return indirectInlinees;
   }
 
@@ -160,7 +162,7 @@
       if (eligibleClass.classInitializationMayHaveSideEffects(
           appView,
           // Types that are a super type of the current context are guaranteed to be initialized.
-          type -> appView.isSubtype(method.holder(), type).isTrue(),
+          type -> appView.isSubtype(method.getHolderType(), type).isTrue(),
           Sets.newIdentityHashSet())) {
         return EligibilityStatus.HAS_CLINIT;
       }
@@ -170,7 +172,7 @@
     assert root.isStaticGet();
 
     StaticGet staticGet = root.asStaticGet();
-    if (staticGet.instructionMayHaveSideEffects(appView, method.holder())) {
+    if (staticGet.instructionMayHaveSideEffects(appView, method.getHolderType())) {
       return EligibilityStatus.RETRIEVAL_MAY_HAVE_SIDE_EFFECTS;
     }
     DexEncodedField field = appView.appInfo().resolveField(staticGet.getField());
@@ -265,15 +267,17 @@
 
         if (user.isInvokeMethod()) {
           InvokeMethod invokeMethod = user.asInvokeMethod();
-          DexEncodedMethod singleTarget = invokeMethod.lookupSingleTarget(appView, method.holder());
-          if (singleTarget == null) {
+          DexEncodedMethod singleTargetMethod =
+              invokeMethod.lookupSingleTarget(appView, method.getHolderType());
+          if (singleTargetMethod == null) {
             return user; // Not eligible.
           }
 
-          if (isEligibleLibraryMethodCall(invokeMethod, singleTarget)) {
+          if (isEligibleLibraryMethodCall(invokeMethod, singleTargetMethod)) {
             continue;
           }
 
+          ProgramMethod singleTarget = singleTargetMethod.asProgramMethod(appView);
           if (!isEligibleSingleTarget(singleTarget)) {
             return user; // Not eligible.
           }
@@ -463,8 +467,11 @@
               throw new IllegalClassInlinerStateException();
             }
 
+            ProgramMethod singleTargetMethod =
+                new ProgramMethod(
+                    appView.definitionForHolder(singleTarget).asProgramClass(), singleTarget);
             methodCallsOnInstance.put(
-                invoke, new InliningInfo(singleTarget, root.asNewInstance().clazz));
+                invoke, new InliningInfo(singleTargetMethod, root.asNewInstance().clazz));
             break;
           }
         }
@@ -506,8 +513,7 @@
             continue;
           }
 
-          DexEncodedMethod singleTarget =
-              invoke.lookupSingleTarget(appView, code.method().holder());
+          ProgramMethod singleTarget = invoke.lookupSingleProgramTarget(appView, method);
           if (singleTarget == null || !indirectMethodCallsOnInstance.contains(singleTarget)) {
             throw new IllegalClassInlinerStateException();
           }
@@ -568,7 +574,7 @@
           continue;
         }
 
-        DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.holder());
+        DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.getHolderType());
         if (singleTarget != null) {
           Predicate<InvokeMethod> noSideEffectsPredicate =
               dexItemFactory.libraryMethodsWithoutSideEffects.getOrDefault(
@@ -618,7 +624,7 @@
 
       throw new Unreachable(
           "Unexpected usage left in method `"
-              + method.method.toSourceString()
+              + method.toSourceString()
               + "` after inlining: "
               + user);
     }
@@ -650,7 +656,7 @@
 
       throw new Unreachable(
           "Unexpected usage left in method `"
-              + method.method.toSourceString()
+              + method.toSourceString()
               + "` after inlining: "
               + user);
     }
@@ -689,7 +695,7 @@
       if (!user.isInstancePut()) {
         throw new Unreachable(
             "Unexpected usage left in method `"
-                + method.method.toSourceString()
+                + method.toSourceString()
                 + "` after field reads removed: "
                 + user);
       }
@@ -699,7 +705,7 @@
       if (field == null) {
         throw new Unreachable(
             "Unexpected field write left in method `"
-                + method.method.toSourceString()
+                + method.toSourceString()
                 + "` after field reads removed: "
                 + user);
       }
@@ -707,8 +713,7 @@
     }
   }
 
-  private InliningInfo isEligibleConstructorCall(
-      InvokeDirect invoke, DexEncodedMethod singleTarget) {
+  private InliningInfo isEligibleConstructorCall(InvokeDirect invoke, ProgramMethod singleTarget) {
     assert dexItemFactory.isConstructor(invoke.getInvokedMethod());
     assert isEligibleSingleTarget(singleTarget);
 
@@ -735,7 +740,7 @@
 
     // Check that the `eligibleInstance` does not escape via the constructor.
     InstanceInitializerInfo instanceInitializerInfo =
-        singleTarget.getOptimizationInfo().getInstanceInitializerInfo();
+        singleTarget.getDefinition().getOptimizationInfo().getInstanceInitializerInfo();
     if (instanceInitializerInfo.receiverMayEscapeOutsideConstructorChain()) {
       return null;
     }
@@ -746,21 +751,22 @@
       if (parent == null) {
         return null;
       }
-      DexEncodedMethod encodedParent = appView.definitionFor(parent);
+      ProgramMethod encodedParent = asProgramMethodOrNull(appView.definitionFor(parent), appView);
       if (encodedParent == null) {
         return null;
       }
       if (methodProcessor.isProcessedConcurrently(encodedParent)) {
         return null;
       }
-      if (!encodedParent.isInliningCandidate(
+      DexEncodedMethod encodedParentMethod = encodedParent.getDefinition();
+      if (!encodedParentMethod.isInliningCandidate(
           method,
           Reason.SIMPLE,
           appView.appInfo(),
           NopWhyAreYouNotInliningReporter.getInstance())) {
         return null;
       }
-      parent = encodedParent.getOptimizationInfo().getInstanceInitializerInfo().getParent();
+      parent = encodedParentMethod.getOptimizationInfo().getInstanceInitializerInfo().getParent();
     }
 
     return new InliningInfo(singleTarget, eligibleClass.type);
@@ -848,7 +854,7 @@
 
   private InliningInfo isEligibleDirectVirtualMethodCall(
       InvokeMethodWithReceiver invoke,
-      DexEncodedMethod singleTarget,
+      ProgramMethod singleTarget,
       Set<Instruction> indirectUsers,
       Supplier<InliningOracle> defaultOracle) {
     assert isEligibleSingleTarget(singleTarget);
@@ -862,7 +868,7 @@
     }
 
     // TODO(b/141719453): Should not constrain library overrides if all instantiations are inlined.
-    if (singleTarget.isLibraryMethodOverride().isTrue()) {
+    if (singleTarget.getDefinition().isLibraryMethodOverride().isTrue()) {
       InliningOracle inliningOracle = defaultOracle.get();
       if (!inliningOracle.passesInliningConstraints(
           invoke, singleTarget, Reason.SIMPLE, NopWhyAreYouNotInliningReporter.getInstance())) {
@@ -880,18 +886,20 @@
     }
 
     ClassInlinerEligibilityInfo eligibility =
-        singleTarget.getOptimizationInfo().getClassInlinerEligibility();
+        singleTarget.getDefinition().getOptimizationInfo().getClassInlinerEligibility();
     if (eligibility.callsReceiver.size() > 1) {
       return null;
     }
     if (!eligibility.callsReceiver.isEmpty()) {
       assert eligibility.callsReceiver.get(0).getFirst() == Invoke.Type.VIRTUAL;
       DexMethod indirectlyInvokedMethod = eligibility.callsReceiver.get(0).getSecond();
-      DexEncodedMethod indirectSingleTarget =
-          appView.appInfo().resolveMethod(eligibleClass, indirectlyInvokedMethod).getSingleTarget();
-      if (indirectSingleTarget == null) {
+      ResolutionResult resolutionResult =
+          appView.appInfo().resolveMethod(eligibleClass, indirectlyInvokedMethod);
+      if (!resolutionResult.isSingleResolution()) {
         return null;
       }
+      ProgramMethod indirectSingleTarget =
+          resolutionResult.asSingleResolution().getResolutionPair().asProgramMethod();
       if (!isEligibleIndirectVirtualMethodCall(indirectlyInvokedMethod, indirectSingleTarget)) {
         return null;
       }
@@ -902,17 +910,19 @@
   }
 
   private boolean isEligibleIndirectVirtualMethodCall(DexMethod invokedMethod) {
-    DexEncodedMethod singleTarget =
-        appView.appInfo().resolveMethod(eligibleClass, invokedMethod).getSingleTarget();
+    ProgramMethod singleTarget =
+        asProgramMethodOrNull(
+            appView.appInfo().resolveMethod(eligibleClass, invokedMethod).getSingleTarget(),
+            appView);
     return isEligibleIndirectVirtualMethodCall(invokedMethod, singleTarget);
   }
 
   private boolean isEligibleIndirectVirtualMethodCall(
-      DexMethod invokedMethod, DexEncodedMethod singleTarget) {
+      DexMethod invokedMethod, ProgramMethod singleTarget) {
     if (!isEligibleSingleTarget(singleTarget)) {
       return false;
     }
-    if (singleTarget.isLibraryMethodOverride().isTrue()) {
+    if (singleTarget.getDefinition().isLibraryMethodOverride().isTrue()) {
       return false;
     }
     return isEligibleVirtualMethodCall(
@@ -926,7 +936,7 @@
   private boolean isEligibleVirtualMethodCall(
       InvokeMethodWithReceiver invoke,
       DexMethod callee,
-      DexEncodedMethod singleTarget,
+      ProgramMethod singleTarget,
       Predicate<ClassInlinerEligibilityInfo> eligibilityAcceptanceCheck) {
     assert isEligibleSingleTarget(singleTarget);
 
@@ -939,14 +949,14 @@
       return false;
     }
 
-    if (!singleTarget.isNonPrivateVirtualMethod()) {
+    if (!singleTarget.getDefinition().isNonPrivateVirtualMethod()) {
       return false;
     }
-    if (method == singleTarget) {
+    if (method.getDefinition() == singleTarget.getDefinition()) {
       return false; // Don't inline itself.
     }
 
-    MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
+    MethodOptimizationInfo optimizationInfo = singleTarget.getDefinition().getOptimizationInfo();
     ClassInlinerEligibilityInfo eligibility = optimizationInfo.getClassInlinerEligibility();
     if (eligibility == null) {
       return false;
@@ -1013,7 +1023,7 @@
   //   -- method itself can be inlined
   //
   private boolean isExtraMethodCallEligible(
-      InvokeMethod invoke, DexEncodedMethod singleTarget, Supplier<InliningOracle> defaultOracle) {
+      InvokeMethod invoke, ProgramMethod singleTarget, Supplier<InliningOracle> defaultOracle) {
     // Don't consider constructor invocations and super calls, since we don't want to forcibly
     // inline them.
     assert isExtraMethodCall(invoke);
@@ -1035,10 +1045,11 @@
       }
     }
 
-    MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
+    MethodOptimizationInfo optimizationInfo = singleTarget.getDefinition().getOptimizationInfo();
 
     // Go through all arguments, see if all usages of eligibleInstance are good.
-    if (!isEligibleParameterUsages(invoke, arguments, singleTarget, defaultOracle)) {
+    if (!isEligibleParameterUsages(
+        invoke, arguments, singleTarget.getDefinition(), defaultOracle)) {
       return false;
     }
 
@@ -1155,16 +1166,23 @@
       }
 
       // Check if the method is inline-able by standard inliner.
-      DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.holder());
+      DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.getHolderType());
       if (singleTarget == null) {
         return false;
       }
 
+      DexProgramClass holder = asProgramClassOrNull(appView.definitionForHolder(singleTarget));
+      if (holder == null) {
+        return false;
+      }
+
+      ProgramMethod singleTargetMethod = new ProgramMethod(holder, singleTarget);
+
       InliningOracle oracle = defaultOracle.get();
       InlineAction inlineAction =
           oracle.computeInlining(
               invoke,
-              singleTarget,
+              singleTargetMethod,
               method,
               ClassInitializationAnalysis.trivial(),
               NopWhyAreYouNotInliningReporter.getInstance());
@@ -1188,15 +1206,12 @@
     return initializerInfo.receiverNeverEscapesOutsideConstructorChain();
   }
 
-  private boolean exemptFromInstructionLimit(DexEncodedMethod inlinee) {
-    DexType inlineeHolder = inlinee.holder();
-    DexClass inlineeClass = appView.definitionFor(inlineeHolder);
-    assert inlineeClass != null;
-    KotlinClassLevelInfo kotlinInfo = inlineeClass.getKotlinInfo();
+  private boolean exemptFromInstructionLimit(ProgramMethod inlinee) {
+    KotlinClassLevelInfo kotlinInfo = inlinee.getHolder().getKotlinInfo();
     return kotlinInfo.isSyntheticClass() && kotlinInfo.asSyntheticClass().isLambda();
   }
 
-  private void markSizeForInlining(InvokeMethod invoke, DexEncodedMethod inlinee) {
+  private void markSizeForInlining(InvokeMethod invoke, ProgramMethod inlinee) {
     assert !methodProcessor.isProcessedConcurrently(inlinee);
     if (!exemptFromInstructionLimit(inlinee)) {
       if (invoke != null) {
@@ -1207,18 +1222,20 @@
     }
   }
 
-  private boolean isEligibleSingleTarget(DexEncodedMethod singleTarget) {
+  private boolean isEligibleSingleTarget(ProgramMethod singleTarget) {
     if (singleTarget == null) {
       return false;
     }
-    if (!singleTarget.isProgramMethod(appView)) {
-      return false;
-    }
     if (methodProcessor.isProcessedConcurrently(singleTarget)) {
       return false;
     }
-    if (!singleTarget.isInliningCandidate(
-        method, Reason.SIMPLE, appView.appInfo(), NopWhyAreYouNotInliningReporter.getInstance())) {
+    if (!singleTarget
+        .getDefinition()
+        .isInliningCandidate(
+            method,
+            Reason.SIMPLE,
+            appView.appInfo(),
+            NopWhyAreYouNotInliningReporter.getInstance())) {
       // If `singleTarget` is not an inlining candidate, we won't be able to inline it here.
       //
       // Note that there may be some false negatives here since the method may
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 cf9c904..06c2894 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
@@ -53,6 +53,7 @@
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -73,7 +74,7 @@
   private final DexItemFactory factory;
   // Map the enum candidates with their dependencies, i.e., the methods to reprocess for the given
   // enum if the optimization eventually decides to unbox it.
-  private final Map<DexType, Set<DexEncodedMethod>> enumsUnboxingCandidates;
+  private final Map<DexType, ProgramMethodSet> enumsUnboxingCandidates;
 
   private EnumUnboxingRewriter enumUnboxerRewriter;
 
@@ -184,11 +185,11 @@
     }
     if (!eligibleEnums.isEmpty()) {
       for (DexType eligibleEnum : eligibleEnums) {
-        Set<DexEncodedMethod> dependencies = enumsUnboxingCandidates.get(eligibleEnum);
+        ProgramMethodSet dependencies = enumsUnboxingCandidates.get(eligibleEnum);
         // If dependencies is null, it means the enum is not eligible (It has been marked as
         // unboxable by this thread or another one), so we do not need to record dependencies.
         if (dependencies != null) {
-          dependencies.add(code.method());
+          dependencies.add(code.context());
         }
       }
     }
@@ -313,7 +314,6 @@
           appView
               .appInfo()
               .rewrittenWithLens(appView.appInfo().app().asDirect(), enumUnboxingLens));
-      classStaticizer.filterCandidates();
       // Update optimization info.
       feedback.fixupOptimizationInfos(
           appView,
@@ -609,9 +609,9 @@
   }
 
   @Override
-  public Set<DexEncodedMethod> methodsToRevisit() {
-    Set<DexEncodedMethod> toReprocess = Sets.newIdentityHashSet();
-    for (Set<DexEncodedMethod> methods : enumsUnboxingCandidates.values()) {
+  public ProgramMethodSet methodsToRevisit() {
+    ProgramMethodSet toReprocess = ProgramMethodSet.create();
+    for (ProgramMethodSet methods : enumsUnboxingCandidates.values()) {
       toReprocess.addAll(methods);
     }
     return toReprocess;
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 5412037..d138d95 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
@@ -18,9 +18,8 @@
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxer.Reason;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.google.common.collect.Sets;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.Map;
-import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 class EnumUnboxingCandidateAnalysis {
@@ -28,7 +27,7 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final EnumUnboxer enumUnboxer;
   private final DexItemFactory factory;
-  private Map<DexType, Set<DexEncodedMethod>> enumToUnboxCandidates = new ConcurrentHashMap<>();
+  private Map<DexType, ProgramMethodSet> enumToUnboxCandidates = new ConcurrentHashMap<>();
 
   EnumUnboxingCandidateAnalysis(AppView<AppInfoWithLiveness> appView, EnumUnboxer enumUnboxer) {
     this.appView = appView;
@@ -36,10 +35,10 @@
     factory = appView.dexItemFactory();
   }
 
-  Map<DexType, Set<DexEncodedMethod>> findCandidates() {
+  Map<DexType, ProgramMethodSet> findCandidates() {
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       if (isEnumUnboxingCandidate(clazz)) {
-        enumToUnboxCandidates.put(clazz.type, Sets.newConcurrentHashSet());
+        enumToUnboxCandidates.put(clazz.type, ProgramMethodSet.createConcurrent());
       }
     }
     removeEnumsInAnnotations();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueInfoMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueInfoMapCollector.java
index 953514d..c94be2e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueInfoMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueInfoMapCollector.java
@@ -4,13 +4,13 @@
 package com.android.tools.r8.ir.optimize.enums;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
@@ -54,8 +54,8 @@
     if (!clazz.accessFlags.isEnum() || clazz.isNotProgramClass() || !clazz.hasClassInitializer()) {
       return;
     }
-    DexEncodedMethod initializer = clazz.getClassInitializer();
-    IRCode code = initializer.getCode().buildIR(initializer, appView, clazz.origin);
+    ProgramMethod initializer = clazz.getProgramClassInitializer();
+    IRCode code = initializer.buildIR(appView);
     LinkedHashMap<DexField, EnumValueInfo> enumValueInfoMap = new LinkedHashMap<>();
     for (StaticPut staticPut : code.<StaticPut>instructions(Instruction::isStaticPut)) {
       if (staticPut.getField().type != clazz.type) {
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 14a3800..23b74b1 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
@@ -289,8 +289,7 @@
       return;
     }
 
-    boolean synchronizedVirtualMethod =
-        method.accessFlags.isSynchronized() && method.isVirtualMethod();
+    boolean synchronizedVirtualMethod = method.isSynchronized() && method.isVirtualMethod();
 
     feedback.setClassInlinerEligibility(
         method,
@@ -1042,7 +1041,7 @@
       return;
     }
     boolean mayHaveSideEffects;
-    if (method.accessFlags.isSynchronized()) {
+    if (method.isSynchronized()) {
       // If the method is synchronized then it acquires a lock.
       mayHaveSideEffects = true;
     } else if (method.isInstanceInitializer() && hasNonTrivialFinalizeMethod(context)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
index 5c45a37..ae543a2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
@@ -6,6 +6,8 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.conversion.CallSiteInformation;
 import com.android.tools.r8.ir.optimize.Inliner;
@@ -29,25 +31,27 @@
 
   @Override
   public Reason computeInliningReason(
-      InvokeMethod invoke, DexEncodedMethod target, DexEncodedMethod context) {
-    if (target.getOptimizationInfo().forceInline()
+      InvokeMethod invoke, ProgramMethod target, ProgramMethod context) {
+    DexEncodedMethod targetMethod = target.getDefinition();
+    DexMethod targetReference = target.getReference();
+    if (targetMethod.getOptimizationInfo().forceInline()
         || (appView.appInfo().hasLiveness()
-            && appView.withLiveness().appInfo().forceInline.contains(target.method))) {
-      assert !appView.appInfo().neverInline.contains(target.method);
+            && appView.withLiveness().appInfo().forceInline.contains(targetReference))) {
+      assert !appView.appInfo().neverInline.contains(targetReference);
       return Reason.FORCE;
     }
     if (appView.appInfo().hasLiveness()
-        && appView.withLiveness().appInfo().alwaysInline.contains(target.method)) {
+        && appView.withLiveness().appInfo().alwaysInline.contains(targetReference)) {
       return Reason.ALWAYS;
     }
     if (appView.options().disableInliningOfLibraryMethodOverrides
-        && target.isLibraryMethodOverride().isTrue()) {
+        && targetMethod.isLibraryMethodOverride().isTrue()) {
       // This method will always have an implicit call site from the library, so we won't be able to
       // remove it after inlining even if we have single or dual call site information from the
       // program.
       return Reason.SIMPLE;
     }
-    if (callSiteInformation.hasSingleCallSite(target.method)) {
+    if (callSiteInformation.hasSingleCallSite(target)) {
       return Reason.SINGLE_CALLER;
     }
     if (isDoubleInliningTarget(target)) {
@@ -56,11 +60,11 @@
     return Reason.SIMPLE;
   }
 
-  private boolean isDoubleInliningTarget(DexEncodedMethod candidate) {
+  private boolean isDoubleInliningTarget(ProgramMethod candidate) {
     // 10 is found from measuring.
-    if (callSiteInformation.hasDoubleCallSite(candidate.method)
+    if (callSiteInformation.hasDoubleCallSite(candidate)
         || inliner.isDoubleInlineSelectedTarget(candidate)) {
-      return candidate.getCode().estimatedSizeForInliningAtMost(10);
+      return candidate.getDefinition().getCode().estimatedSizeForInliningAtMost(10);
     }
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java
index 2b4c072..ae23a74 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.inliner;
 
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 
@@ -18,7 +18,7 @@
 
   @Override
   public Reason computeInliningReason(
-      InvokeMethod invoke, DexEncodedMethod target, DexEncodedMethod context) {
+      InvokeMethod invoke, ProgramMethod target, ProgramMethod context) {
     return reason;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
index 608c41f..85cb117 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.ir.optimize.inliner;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Position;
@@ -18,32 +18,32 @@
 public class InliningIRProvider {
 
   private final AppView<?> appView;
-  private final DexEncodedMethod context;
+  private final ProgramMethod context;
   private final ValueNumberGenerator valueNumberGenerator;
   private final MethodProcessor methodProcessor;
 
   private final Map<InvokeMethod, IRCode> cache = new IdentityHashMap<>();
 
   public InliningIRProvider(
-      AppView<?> appView, DexEncodedMethod context, IRCode code, MethodProcessor methodProcessor) {
+      AppView<?> appView, ProgramMethod context, IRCode code, MethodProcessor methodProcessor) {
     this.appView = appView;
     this.context = context;
     this.valueNumberGenerator = code.valueNumberGenerator;
     this.methodProcessor = methodProcessor;
   }
 
-  public IRCode getInliningIR(InvokeMethod invoke, DexEncodedMethod method) {
+  public IRCode getInliningIR(InvokeMethod invoke, ProgramMethod method) {
     IRCode cached = cache.remove(invoke);
     if (cached != null) {
       return cached;
     }
     Position position = Position.getPositionForInlining(appView, invoke, context);
-    Origin origin = appView.appInfo().originFor(method.holder());
+    Origin origin = method.getOrigin();
     return method.buildInliningIR(
         context, appView, valueNumberGenerator, position, origin, methodProcessor);
   }
 
-  public IRCode getAndCacheInliningIR(InvokeMethod invoke, DexEncodedMethod method) {
+  public IRCode getAndCacheInliningIR(InvokeMethod invoke, ProgramMethod method) {
     IRCode inliningIR = getInliningIR(invoke, method);
     cacheInliningIR(invoke, inliningIR);
     return inliningIR;
@@ -59,7 +59,7 @@
     return true;
   }
 
-  public boolean shouldApplyCodeRewritings(DexEncodedMethod method) {
+  public boolean shouldApplyCodeRewritings(ProgramMethod method) {
     return methodProcessor.shouldApplyCodeRewritings(method);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java
index 6e8e1aa..f2ae52a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningReasonStrategy.java
@@ -4,12 +4,11 @@
 
 package com.android.tools.r8.ir.optimize.inliner;
 
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 
 public interface InliningReasonStrategy {
 
-  Reason computeInliningReason(
-      InvokeMethod invoke, DexEncodedMethod target, DexEncodedMethod context);
+  Reason computeInliningReason(InvokeMethod invoke, ProgramMethod target, ProgramMethod context);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
index eb8b742..92635f2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
@@ -6,11 +6,11 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-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.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.ConstClass;
@@ -158,19 +158,19 @@
   private final LambdaTypeVisitor lambdaChecker;
 
   // Specify the context of the current instruction: method/code/blocks/instructions.
-  public final DexEncodedMethod method;
+  public final ProgramMethod method;
   public final IRCode code;
   public final ListIterator<BasicBlock> blocks;
   private InstructionListIterator instructions;
 
   // The inlining context (caller), if any.
-  private final DexEncodedMethod context;
+  private final ProgramMethod context;
 
   CodeProcessor(
       AppView<AppInfoWithLiveness> appView,
       Function<DexType, Strategy> strategyProvider,
       LambdaTypeVisitor lambdaChecker,
-      DexEncodedMethod method,
+      ProgramMethod method,
       IRCode code) {
     this(appView, strategyProvider, lambdaChecker, method, code, null);
   }
@@ -179,9 +179,9 @@
       AppView<AppInfoWithLiveness> appView,
       Function<DexType, Strategy> strategyProvider,
       LambdaTypeVisitor lambdaChecker,
-      DexEncodedMethod method,
+      ProgramMethod method,
       IRCode code,
-      DexEncodedMethod context) {
+      ProgramMethod context) {
     this.appView = appView;
     this.strategyProvider = strategyProvider;
     this.factory = appView.dexItemFactory();
@@ -218,7 +218,7 @@
 
   private boolean shouldRewrite(DexType type) {
     // Rewrite references to lambda classes if we are outside the class.
-    return type != (context != null ? context : method).holder();
+    return type != (context != null ? context : method).getHolderType();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index 63d2c1d..2a5e9cd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -10,12 +10,14 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 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.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.classmerging.HorizontallyMergedLambdaClasses;
 import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
@@ -50,6 +52,8 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.Sets;
@@ -67,7 +71,6 @@
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.function.Function;
-import java.util.stream.Collectors;
 
 // Merging lambda classes into single lambda group classes. There are three flavors
 // of lambdas we are dealing with:
@@ -97,19 +100,19 @@
   private abstract static class Mode {
 
     void rewriteCode(
-        DexEncodedMethod method,
+        ProgramMethod method,
         IRCode code,
         Inliner inliner,
-        DexEncodedMethod context,
+        ProgramMethod context,
         InliningIRProvider provider) {}
 
-    void analyzeCode(DexEncodedMethod method, IRCode code) {}
+    void analyzeCode(ProgramMethod method, IRCode code) {}
   }
 
   private class AnalyzeMode extends Mode {
 
     @Override
-    void analyzeCode(DexEncodedMethod method, IRCode code) {
+    void analyzeCode(ProgramMethod method, IRCode code) {
       new AnalysisStrategy(method, code).processCode();
     }
   }
@@ -128,27 +131,24 @@
 
     @Override
     void rewriteCode(
-        DexEncodedMethod method,
+        ProgramMethod method,
         IRCode code,
         Inliner inliner,
-        DexEncodedMethod context,
+        ProgramMethod context,
         InliningIRProvider provider) {
-      DexProgramClass clazz = appView.definitionFor(method.holder()).asProgramClass();
-      assert clazz != null;
-
-      LambdaGroup lambdaGroup = lambdaGroups.get(clazz);
+      LambdaGroup lambdaGroup = lambdaGroups.get(method.getHolder());
       if (lambdaGroup == null) {
         // Only rewrite the methods that have not been synthesized for the lambda group classes.
         new ApplyStrategy(method, code, context, optimizationInfoFixer).processCode();
         return;
       }
 
-      if (method.isInitializer()) {
+      if (method.getDefinition().isInitializer()) {
         // Should not require rewriting.
         return;
       }
 
-      assert method.isNonPrivateVirtualMethod();
+      assert method.getDefinition().isNonPrivateVirtualMethod();
       assert context == null;
 
       Map<InvokeVirtual, InliningInfo> invokesToInline = new IdentityHashMap<>();
@@ -160,9 +160,10 @@
           ResolutionResult resolution =
               appView.appInfo().resolveMethod(holder, invokedMethod, false);
           assert resolution.isSingleResolution();
-          DexEncodedMethod singleTarget = resolution.getSingleTarget();
+          ProgramMethod singleTarget =
+              resolution.asSingleResolution().getResolutionPair().asProgramMethod();
           assert singleTarget != null;
-          invokesToInline.put(invoke, new InliningInfo(singleTarget, singleTarget.holder()));
+          invokesToInline.put(invoke, new InliningInfo(singleTarget, singleTarget.getHolderType()));
         }
       }
 
@@ -194,7 +195,8 @@
   // we mark a method for further processing, and then invalidate the only lambda referenced
   // from it. In this case we will reprocess method that does not need patching, but it
   // should not be happening very frequently and we ignore possible overhead.
-  private final Set<DexEncodedMethod> methodsToReprocess = Sets.newIdentityHashSet();
+  private final LongLivedProgramMethodSetBuilder methodsToReprocess =
+      new LongLivedProgramMethodSetBuilder();
 
   private final AppView<AppInfoWithLiveness> appView;
   private final Kotlin kotlin;
@@ -236,7 +238,7 @@
     return lambdas.get(lambda);
   }
 
-  private synchronized void queueForProcessing(DexEncodedMethod method) {
+  private synchronized void queueForProcessing(ProgramMethod method) {
     methodsToReprocess.add(method);
   }
 
@@ -291,7 +293,7 @@
    *   <li>in APPLY mode does nothing.
    * </ol>
    */
-  public final void analyzeCode(DexEncodedMethod method, IRCode code) {
+  public final void analyzeCode(ProgramMethod method, IRCode code) {
     if (mode != null) {
       mode.analyzeCode(method, code);
     }
@@ -308,10 +310,10 @@
    * </ol>
    */
   public final void rewriteCode(
-      DexEncodedMethod method, IRCode code, Inliner inliner, MethodProcessor methodProcessor) {
+      ProgramMethod method, IRCode code, Inliner inliner, MethodProcessor methodProcessor) {
     if (mode != null) {
       mode.rewriteCode(
-          method,
+          code.context(),
           code,
           inliner,
           null,
@@ -320,12 +322,12 @@
   }
 
   /**
-   * Similar to {@link #rewriteCode(DexEncodedMethod, IRCode, Inliner, MethodProcessor)}, but for
+   * Similar to {@link #rewriteCode(ProgramMethod, IRCode, Inliner, MethodProcessor)}, but for
    * rewriting code for inlining. The {@param context} is the caller that {@param method} is being
    * inlined into.
    */
   public final void rewriteCodeForInlining(
-      DexEncodedMethod method, IRCode code, DexEncodedMethod context, InliningIRProvider provider) {
+      ProgramMethod method, IRCode code, ProgramMethod context, InliningIRProvider provider) {
     if (mode != null) {
       mode.rewriteCode(method, code, null, context, provider);
     }
@@ -453,12 +455,11 @@
     if (methodsToReprocess.isEmpty()) {
       return;
     }
-    Set<DexEncodedMethod> methods =
-        methodsToReprocess.stream()
-            .map(method -> appView.graphLense().mapDexEncodedMethod(method, appView))
-            .collect(Collectors.toSet());
+    ProgramMethodSet methods = methodsToReprocess.build(appView);
     converter.processMethodsConcurrently(methods, executorService);
-    assert methods.stream().allMatch(DexEncodedMethod::isProcessed);
+    assert methods.stream()
+        .map(DexClassAndMethod::getDefinition)
+        .allMatch(DexEncodedMethod::isProcessed);
   }
 
   private void analyzeClass(DexProgramClass clazz) {
@@ -491,7 +492,7 @@
   }
 
   private final class AnalysisStrategy extends CodeProcessor {
-    private AnalysisStrategy(DexEncodedMethod method, IRCode code) {
+    private AnalysisStrategy(ProgramMethod method, IRCode code) {
       super(
           LambdaMerger.this.appView,
           LambdaMerger.this::strategyProvider,
@@ -543,9 +544,9 @@
     private final Set<Value> typeAffectedValues = Sets.newIdentityHashSet();
 
     private ApplyStrategy(
-        DexEncodedMethod method,
+        ProgramMethod method,
         IRCode code,
-        DexEncodedMethod context,
+        ProgramMethod context,
         LambdaMergerOptimizationInfoFixer optimizationInfoFixer) {
       super(
           LambdaMerger.this.appView,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
index 4d79186..112b37c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
@@ -53,8 +53,8 @@
     // static class initializer.
     return field.name == context.kotlin.functional.kotlinStyleLambdaInstanceName
         && lambda == field.type
-        && context.factory.isClassConstructor(context.method.method)
-        && context.method.holder() == lambda;
+        && context.method.getDefinition().isClassInitializer()
+        && context.method.getHolderType() == lambda;
   }
 
   @Override
@@ -69,10 +69,10 @@
   @Override
   public boolean isValidInstanceFieldWrite(CodeProcessor context, DexField field) {
     DexType lambda = field.holder;
-    DexMethod method = context.method.method;
+    DexMethod method = context.method.getReference();
     assert group.containsLambda(lambda);
     // Support writes to capture instance fields inside lambda constructor only.
-    return method.holder == lambda && context.factory.isConstructor(method);
+    return method.holder == lambda && context.method.getDefinition().isInstanceInitializer();
   }
 
   @Override
@@ -100,7 +100,7 @@
     // Allow calls to a constructor from other classes if the lambda is singleton,
     // otherwise allow such a call only from the same class static initializer.
     boolean isSingletonLambda = group.isStateless() && group.isSingletonLambda(lambda);
-    return (isSingletonLambda == (context.method.holder() == lambda))
+    return (isSingletonLambda == (context.method.getHolderType() == lambda))
         && invoke.isInvokeDirect()
         && context.factory.isConstructor(method)
         && CaptureSignature.getCaptureSignature(method.proto.parameters).equals(group.id().capture);
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 3311e1d..041cf09 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
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.staticizer;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
@@ -15,6 +17,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.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.IRCode;
@@ -31,11 +34,13 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
@@ -64,7 +69,6 @@
     final AtomicInteger fieldWrites = new AtomicInteger();
     // Number of instances created.
     final AtomicInteger instancesCreated = new AtomicInteger();
-    final Set<DexEncodedMethod> referencedFrom = Sets.newConcurrentHashSet();
     final AtomicReference<DexEncodedMethod> constructor = new AtomicReference<>();
     final AtomicReference<DexEncodedMethod> getter = new AtomicReference<>();
 
@@ -86,8 +90,8 @@
       return singletonField.holder();
     }
 
-    DexClass hostClass() {
-      DexClass hostClass = appView.definitionFor(hostType());
+    DexProgramClass hostClass() {
+      DexProgramClass hostClass = asProgramClassOrNull(appView.definitionFor(hostType()));
       assert hostClass != null;
       return hostClass;
     }
@@ -96,17 +100,11 @@
       candidates.remove(candidate.type);
       return null;
     }
-
-    void filterMethods() {
-      Set<DexEncodedMethod> newReferencedFrom = Sets.newIdentityHashSet();
-      for (DexEncodedMethod dexEncodedMethod : referencedFrom) {
-        newReferencedFrom.add(appView.graphLense().mapDexEncodedMethod(dexEncodedMethod, appView));
-      }
-      referencedFrom.clear();
-      referencedFrom.addAll(newReferencedFrom);
-    }
   }
 
+  final Map<CandidateInfo, LongLivedProgramMethodSetBuilder> referencedFrom =
+      new IdentityHashMap<>();
+
   // The map storing all the potential candidates for staticizing.
   final ConcurrentHashMap<DexType, CandidateInfo> candidates = new ConcurrentHashMap<>();
 
@@ -218,10 +216,11 @@
   // or field defined in the class.
   //
   // NOTE: can be called concurrently.
-  public final void examineMethodCode(DexEncodedMethod method, IRCode code) {
+  public final void examineMethodCode(IRCode code) {
+    ProgramMethod method = code.context();
     Set<Instruction> alreadyProcessed = Sets.newIdentityHashSet();
 
-    CandidateInfo receiverClassCandidateInfo = candidates.get(method.holder());
+    CandidateInfo receiverClassCandidateInfo = candidates.get(method.getHolderType());
     Value receiverValue = code.getThis(); // NOTE: is null for static methods.
     if (receiverClassCandidateInfo != null) {
       if (receiverValue != null) {
@@ -229,26 +228,28 @@
         // which we will check later), check if all the references to 'this' are valid
         // (the call will invalidate the candidate if some of them are not valid).
         analyzeAllValueUsers(
-            receiverClassCandidateInfo, receiverValue, factory.isConstructor(method.method));
+            receiverClassCandidateInfo,
+            receiverValue,
+            factory.isConstructor(method.getReference()));
 
         // If the candidate is still valid, ignore all instructions
         // we treat as valid usages on receiver.
-        if (candidates.get(method.holder()) != null) {
+        if (candidates.get(method.getHolderType()) != null) {
           alreadyProcessed.addAll(receiverValue.uniqueUsers());
         }
       } else {
         // We are inside a static method of candidate class.
         // Check if this is a valid getter of the singleton field.
-        if (method.method.proto.returnType == method.holder()) {
+        if (method.getDefinition().returnType() == method.getHolderType()) {
           List<Instruction> examined = isValidGetter(receiverClassCandidateInfo, code);
           if (examined != null) {
             DexEncodedMethod getter = receiverClassCandidateInfo.getter.get();
             if (getter == null) {
-              receiverClassCandidateInfo.getter.set(method);
+              receiverClassCandidateInfo.getter.set(method.getDefinition());
               // Except for static-get and return, iterate other remaining instructions if any.
               alreadyProcessed.addAll(examined);
             } else {
-              assert getter != method;
+              assert getter != method.getDefinition();
               // Not sure how to deal with many getters.
               receiverClassCandidateInfo.invalidate();
             }
@@ -274,7 +275,8 @@
       if (instruction.isNewInstance()) {
         // Check the class being initialized against valid staticizing candidates.
         NewInstance newInstance = instruction.asNewInstance();
-        CandidateInfo candidateInfo = processInstantiation(method, iterator, newInstance);
+        CandidateInfo candidateInfo =
+            processInstantiation(method.getDefinition(), iterator, newInstance);
         if (candidateInfo != null) {
           alreadyProcessed.addAll(newInstance.outValue().aliasedUsers());
           // For host class initializers having eligible instantiation we also want to
@@ -282,14 +284,16 @@
           // This must guarantee that removing field access will not result in missing side
           // effects, otherwise we can still staticize, but cannot remove singleton reads.
           while (iterator.hasNext()) {
-            if (!isAllowedInHostClassInitializer(method.holder(), iterator.next(), code)) {
+            if (!isAllowedInHostClassInitializer(method.getHolderType(), iterator.next(), code)) {
               candidateInfo.preserveRead.set(true);
               iterator.previous();
               break;
             }
             // Ignore just read instruction.
           }
-          candidateInfo.referencedFrom.add(method);
+          referencedFrom
+              .computeIfAbsent(candidateInfo, ignore -> new LongLivedProgramMethodSetBuilder())
+              .add(method);
         }
         continue;
       }
@@ -309,7 +313,9 @@
         // Check the field being read: make sure all usages are valid.
         CandidateInfo info = processStaticFieldRead(instruction.asStaticGet());
         if (info != null) {
-          info.referencedFrom.add(method);
+          referencedFrom
+              .computeIfAbsent(info, ignore -> new LongLivedProgramMethodSetBuilder())
+              .add(method);
           // If the candidate is still valid, ignore all usages in further analysis.
           Value value = instruction.outValue();
           if (value != null) {
@@ -323,7 +329,9 @@
         // Check if it is a static singleton getter.
         CandidateInfo info = processInvokeStatic(instruction.asInvokeStatic());
         if (info != null) {
-          info.referencedFrom.add(method);
+          referencedFrom
+              .computeIfAbsent(info, ignore -> new LongLivedProgramMethodSetBuilder())
+              .add(method);
           // If the candidate is still valid, ignore all usages in further analysis.
           Value value = instruction.outValue();
           if (value != null) {
@@ -659,17 +667,6 @@
     return false;
   }
 
-  // Methods may have their signature changed in-between the IR processing rounds, leading to
-  // duplicates where one version is the outdated version. Remove these.
-  // This also ensures no unboxed enum are staticized, if that would be the case, then
-  // the candidate would need to be removed from the candidate list.
-  public void filterCandidates() {
-    for (Map.Entry<DexType, CandidateInfo> entry : candidates.entrySet()) {
-      assert !appView.unboxedEnums().containsEnum(entry.getKey());
-      entry.getValue().filterMethods();
-    }
-  }
-
   // Perform staticizing candidates:
   //
   //  1. After filtering candidates based on usage, finalize the list of candidates by
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 845a01c..1e7fb3c 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
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.staticizer;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 
 import com.android.tools.r8.graph.AppView;
@@ -17,6 +18,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -36,10 +38,11 @@
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer.CandidateInfo;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableList;
@@ -66,13 +69,15 @@
   private final ClassStaticizer classStaticizer;
   private final IRConverter converter;
 
+  private final ProgramMethodSet methodsToReprocess = ProgramMethodSet.create();
+
   // Optimization order matters, hence a collection that preserves orderings.
   private final Map<DexEncodedMethod, ImmutableList.Builder<BiConsumer<IRCode, MethodProcessor>>>
       processingQueue = new IdentityHashMap<>();
 
-  private final Set<DexEncodedMethod> referencingExtraMethods = Sets.newIdentityHashSet();
+  private final ProgramMethodSet referencingExtraMethods = ProgramMethodSet.create();
   private final Map<DexEncodedMethod, CandidateInfo> hostClassInits = new IdentityHashMap<>();
-  private final Set<DexEncodedMethod> methodsToBeStaticized = Sets.newIdentityHashSet();
+  private final ProgramMethodSet methodsToBeStaticized = ProgramMethodSet.create();
   private final Map<DexField, CandidateInfo> singletonFields = new IdentityHashMap<>();
   private final Map<DexMethod, CandidateInfo> singletonGetters = new IdentityHashMap<>();
   private final Map<DexType, DexType> candidateToHostMapping = new IdentityHashMap<>();
@@ -89,14 +94,21 @@
   final void run(OptimizationFeedback feedback, ExecutorService executorService)
       throws ExecutionException {
     // Filter out candidates based on the information we collected while examining methods.
-    finalEligibilityCheck();
+    Map<CandidateInfo, ProgramMethodSet> materializedReferencedFromCollections =
+        finalEligibilityCheck();
 
     // Prepare interim data.
-    prepareCandidates();
+    prepareCandidates(materializedReferencedFromCollections);
 
     // Enqueue all host class initializers (only remove instantiations).
+    ProgramMethodSet hostClassInitMethods = ProgramMethodSet.create();
+    hostClassInits
+        .values()
+        .forEach(
+            candidateInfo ->
+                hostClassInitMethods.add(candidateInfo.hostClass().getProgramClassInitializer()));
     enqueueMethodsWithCodeOptimizations(
-        hostClassInits.keySet(),
+        hostClassInitMethods,
         optimizations ->
             optimizations
                 .add(this::removeCandidateInstantiation)
@@ -114,13 +126,13 @@
 
     // TODO(b/140767158): Merge the remaining part below.
     // Convert instance methods into static methods with an extra parameter.
-    Set<DexEncodedMethod> methods = staticizeMethodSymbols();
+    ProgramMethodSet methods = staticizeMethodSymbols();
 
     // Process all other methods that may reference singleton fields and call methods on them.
     // (Note that we exclude the former instance methods, but include new static methods created as
     // a result of staticizing.)
     methods.addAll(referencingExtraMethods);
-    methods.addAll(hostClassInits.keySet());
+    methods.addAll(hostClassInitMethods);
     enqueueMethodsWithCodeOptimizations(
         methods,
         optimizations ->
@@ -131,13 +143,17 @@
 
     // Process queued methods with associated optimizations
     processMethodsConcurrently(feedback, executorService);
+
+    // Clear all candidate information now that all candidates have been staticized.
+    classStaticizer.candidates.clear();
   }
 
-  private void finalEligibilityCheck() {
+  private Map<CandidateInfo, ProgramMethodSet> finalEligibilityCheck() {
+    Map<CandidateInfo, ProgramMethodSet> materializedReferencedFromCollections =
+        new IdentityHashMap<>();
     Set<Phi> visited = Sets.newIdentityHashSet();
     Set<Phi> trivialPhis = Sets.newIdentityHashSet();
-    Iterator<Entry<DexType, CandidateInfo>> it =
-        classStaticizer.candidates.entrySet().iterator();
+    Iterator<Entry<DexType, CandidateInfo>> it = classStaticizer.candidates.entrySet().iterator();
     while (it.hasNext()) {
       Entry<DexType, CandidateInfo> entry = it.next();
       DexType candidateType = entry.getKey();
@@ -185,33 +201,41 @@
       }
 
       // CHECK: references to 'this' in instance methods are fixable.
-      boolean fixableThisPointer = true;
-      for (DexEncodedMethod method : candidateClass.methods()) {
-        if (method.isStatic() || factory().isConstructor(method.method)) {
-          continue;
-        }
-        IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.holder()));
-        assert code != null;
-        Value thisValue = code.getThis();
-        assert thisValue != null;
-        visited.clear();
-        trivialPhis.clear();
-        boolean onlyHasTrivialPhis = testAndCollectPhisComposedOfThis(
-            visited, thisValue.uniquePhiUsers(), thisValue, trivialPhis);
-        if (thisValue.hasPhiUsers() && !onlyHasTrivialPhis) {
-          fixableThisPointer = false;
-          break;
-        }
-      }
-      if (!fixableThisPointer) {
+      TraversalContinuation fixableThisPointer =
+          candidateClass.traverseProgramMethods(
+              method -> {
+                IRCode code = method.buildIR(appView);
+                assert code != null;
+                Value thisValue = code.getThis();
+                assert thisValue != null;
+                visited.clear();
+                trivialPhis.clear();
+                boolean onlyHasTrivialPhis =
+                    testAndCollectPhisComposedOfThis(
+                        visited, thisValue.uniquePhiUsers(), thisValue, trivialPhis);
+                if (thisValue.hasPhiUsers() && !onlyHasTrivialPhis) {
+                  return TraversalContinuation.BREAK;
+                }
+                return TraversalContinuation.CONTINUE;
+              },
+              definition -> !definition.isStatic() && !definition.isInstanceInitializer());
+      if (fixableThisPointer.shouldBreak()) {
         it.remove();
         continue;
       }
 
+      ProgramMethodSet referencedFrom;
+      if (classStaticizer.referencedFrom.containsKey(info)) {
+        referencedFrom = classStaticizer.referencedFrom.remove(info).build(appView);
+        materializedReferencedFromCollections.put(info, referencedFrom);
+      } else {
+        referencedFrom = ProgramMethodSet.empty();
+      }
+
       // CHECK: references to field read usages are fixable.
       boolean fixableFieldReads = true;
-      for (DexEncodedMethod method : info.referencedFrom) {
-        IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.holder()));
+      for (ProgramMethod method : referencedFrom) {
+        IRCode code = method.buildIR(appView);
         assert code != null;
         List<Instruction> singletonUsers =
             Streams.stream(code.instructionIterator())
@@ -258,9 +282,11 @@
         continue;
       }
     }
+    return materializedReferencedFromCollections;
   }
 
-  private void prepareCandidates() {
+  private void prepareCandidates(
+      Map<CandidateInfo, ProgramMethodSet> materializedReferencedFromCollections) {
     Set<DexEncodedMethod> removedInstanceMethods = Sets.newIdentityHashSet();
 
     for (CandidateInfo candidate : classStaticizer.candidates.values()) {
@@ -273,40 +299,46 @@
       assert previous == null;
 
       // Collect instance methods to be staticized.
-      for (DexEncodedMethod method : candidateClass.methods()) {
-        if (!method.isStatic()) {
-          removedInstanceMethods.add(method);
-          if (!factory().isConstructor(method.method)) {
-            methodsToBeStaticized.add(method);
-          }
-        }
-      }
+      candidateClass.forEachProgramMethodMatching(
+          definition -> {
+            if (!definition.isStatic()) {
+              removedInstanceMethods.add(definition);
+              return !definition.isInstanceInitializer();
+            }
+            return false;
+          },
+          methodsToBeStaticized::add);
       singletonFields.put(candidate.singletonField.field, candidate);
       DexEncodedMethod getter = candidate.getter.get();
       if (getter != null) {
         singletonGetters.put(getter.method, candidate);
       }
-      assert validMethods(candidate.referencedFrom);
-      referencingExtraMethods.addAll(candidate.referencedFrom);
+      ProgramMethodSet referencedFrom =
+          materializedReferencedFromCollections.getOrDefault(candidate, ProgramMethodSet.empty());
+      assert validMethods(referencedFrom);
+      referencingExtraMethods.addAll(referencedFrom);
     }
 
-    referencingExtraMethods.removeAll(removedInstanceMethods);
+    removedInstanceMethods.forEach(referencingExtraMethods::remove);
   }
 
-  private boolean validMethods(Set<DexEncodedMethod> referencedFrom) {
-    for (DexEncodedMethod dexEncodedMethod : referencedFrom) {
-      DexClass clazz = appView.definitionForHolder(dexEncodedMethod.method);
+  private boolean validMethods(ProgramMethodSet referencedFrom) {
+    for (ProgramMethod method : referencedFrom) {
+      DexClass clazz = appView.definitionForHolder(method.getReference());
       assert clazz != null;
-      assert clazz.lookupMethod(dexEncodedMethod.method) == dexEncodedMethod;
+      assert clazz.lookupMethod(method.getReference()) == method.getDefinition();
     }
     return true;
   }
 
   private void enqueueMethodsWithCodeOptimizations(
-      Iterable<DexEncodedMethod> methods,
+      Iterable<ProgramMethod> methods,
       Consumer<ImmutableList.Builder<BiConsumer<IRCode, MethodProcessor>>> extension) {
-    for (DexEncodedMethod method : methods) {
-      extension.accept(processingQueue.computeIfAbsent(method, ignore -> ImmutableList.builder()));
+    for (ProgramMethod method : methods) {
+      methodsToReprocess.add(method);
+      extension.accept(
+          processingQueue.computeIfAbsent(
+              method.getDefinition(), ignore -> ImmutableList.builder()));
     }
   }
 
@@ -322,24 +354,27 @@
    */
   private void processMethodsConcurrently(
       OptimizationFeedback feedback, ExecutorService executorService) throws ExecutionException {
-    Set<DexEncodedMethod> wave = processingQueue.keySet();
-    OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.getInstance(wave);
+    OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.getInstance(methodsToReprocess);
     methodProcessor.forEachWave(
         method ->
-            forEachMethod(method, processingQueue.get(method).build(), feedback, methodProcessor),
+            forEachMethod(
+                method,
+                processingQueue.get(method.getDefinition()).build(),
+                feedback,
+                methodProcessor),
         executorService);
     // TODO(b/140767158): No need to clear if we can do every thing in one go.
+    methodsToReprocess.clear();
     processingQueue.clear();
   }
 
   // TODO(b/140766440): Should be part or variant of PostProcessor.
   private void forEachMethod(
-      DexEncodedMethod method,
+      ProgramMethod method,
       Collection<BiConsumer<IRCode, MethodProcessor>> codeOptimizations,
       OptimizationFeedback feedback,
       OneTimeMethodProcessor methodProcessor) {
-    Origin origin = appView.appInfo().originFor(method.holder());
-    IRCode code = method.buildIR(appView, origin);
+    IRCode code = method.buildIR(appView);
     codeOptimizations.forEach(codeOptimization -> codeOptimization.accept(code, methodProcessor));
     CodeRewriter.removeAssumeInstructions(appView, code);
     converter.removeDeadCodeAndFinalizeIR(method, code, feedback, Timing.empty());
@@ -686,11 +721,11 @@
     return field;
   }
 
-  private Set<DexEncodedMethod> staticizeMethodSymbols() {
+  private ProgramMethodSet staticizeMethodSymbols() {
     BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
     BiMap<DexField, DexField> fieldMapping = HashBiMap.create();
 
-    Set<DexEncodedMethod> staticizedMethods = Sets.newIdentityHashSet();
+    ProgramMethodSet staticizedMethods = ProgramMethodSet.create();
     for (CandidateInfo candidate : classStaticizer.candidates.values()) {
       DexProgramClass candidateClass = candidate.candidate;
 
@@ -702,7 +737,7 @@
         } else if (!factory().isConstructor(method.method)) {
           DexEncodedMethod staticizedMethod = method.toStaticMethodWithoutThis();
           newDirectMethods.add(staticizedMethod);
-          staticizedMethods.add(staticizedMethod);
+          staticizedMethods.createAndAdd(candidateClass, staticizedMethod);
           methodMapping.put(method.method, staticizedMethod.method);
         }
       }
@@ -712,7 +747,7 @@
       // Consider moving static members from candidate into host.
       DexType hostType = candidate.hostType();
       if (candidateClass.type != hostType) {
-        DexClass hostClass = appView.definitionFor(hostType);
+        DexProgramClass hostClass = asProgramClassOrNull(appView.definitionFor(hostType));
         assert hostClass != null;
         if (!classMembersConflict(candidateClass, hostClass)) {
           // Move all members of the candidate class into host class.
@@ -736,9 +771,10 @@
   }
 
   private void moveMembersIntoHost(
-      Set<DexEncodedMethod> staticizedMethods,
+      ProgramMethodSet staticizedMethods,
       DexProgramClass candidateClass,
-      DexType hostType, DexClass hostClass,
+      DexType hostType,
+      DexProgramClass hostClass,
       BiMap<DexMethod, DexMethod> methodMapping,
       BiMap<DexField, DexField> fieldMapping) {
     candidateToHostMapping.put(candidateClass.type, hostType);
@@ -787,7 +823,7 @@
         if (staticizedMethods.remove(method)) {
           // Properly update staticized methods to reprocess, i.e., add the corresponding one that
           // has just been migrated to the host class.
-          staticizedMethods.add(newMethod);
+          staticizedMethods.createAndAdd(hostClass, newMethod);
         }
         DexMethod originalMethod = methodMapping.inverse().get(method.method);
         if (originalMethod == null) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
index 331f14d..9402fbb 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
@@ -6,8 +6,11 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClasspathMethod;
 import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Position;
@@ -35,30 +38,28 @@
   }
 
   @Override
-  public final IRCode buildIR(DexEncodedMethod encodedMethod, AppView<?> appView, Origin origin) {
-    return IRBuilder.create(
-        encodedMethod,
-        appView,
-        getSourceCodeProvider().get(null),
-        origin).build(encodedMethod);
+  public final IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+    return IRBuilder.create(method, appView, getSourceCodeProvider().get(null), origin)
+        .build(method);
   }
 
   @Override
   public IRCode buildInliningIR(
-      DexEncodedMethod context,
-      DexEncodedMethod encodedMethod,
+      ProgramMethod context,
+      ProgramMethod method,
       AppView<?> appView,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin,
       MethodProcessor methodProcessor) {
     return IRBuilder.createForInlining(
-        encodedMethod,
-        appView,
-        getSourceCodeProvider().get(callerPosition),
-        origin,
-        methodProcessor,
-        valueNumberGenerator).build(context);
+            method,
+            appView,
+            getSourceCodeProvider().get(callerPosition),
+            origin,
+            methodProcessor,
+            valueNumberGenerator)
+        .build(context);
   }
 
   @Override
@@ -67,7 +68,16 @@
   }
 
   @Override
-  public void registerCodeReferences(DexEncodedMethod method, UseRegistry registry) {
+  public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
+    internalRegisterCodeReferences(method, registry);
+  }
+
+  @Override
+  public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) {
+    internalRegisterCodeReferences(method, registry);
+  }
+
+  private void internalRegisterCodeReferences(DexClassAndMethod method, UseRegistry registry) {
     getRegistryCallback().accept(registry);
   }
 
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 9f51daa..4f1e59a 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -4,6 +4,8 @@
 package com.android.tools.r8.jar;
 
 import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
+import static org.objectweb.asm.Opcodes.V1_6;
+import static org.objectweb.asm.Opcodes.V1_8;
 
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
@@ -106,7 +108,7 @@
     for (DexProgramClass clazz : application.classes()) {
       if (clazz.getSynthesizedFrom().isEmpty()
           || options.isDesugaredLibraryCompilation()
-          || options.enableCfInterfaceMethodDesugaring) {
+          || options.cfToCfDesugar) {
         writeClass(clazz, consumer, markerString);
       } else {
         throw new Unimplemented("No support for synthetics in the Java bytecode backend.");
@@ -194,16 +196,16 @@
       // which do not have class file version.
       assert options.testing.enableForceNestBasedAccessDesugaringForTest
           || options.isDesugaredLibraryCompilation()
-          || options.enableCfInterfaceMethodDesugaring;
+          || options.cfToCfDesugar;
       // TODO(b/146424042): We may call static methods on interface classes so we have to go for
-      //  version 52.
-      return options.enableCfInterfaceMethodDesugaring ? 52 : 0;
+      //  Java 8.
+      return options.cfToCfDesugar ? V1_8 : 0;
     }
     return method.getClassFileVersion();
   }
 
   private int getClassFileVersion(DexProgramClass clazz) {
-    int version = clazz.hasClassFileVersion() ? clazz.getInitialClassFileVersion() : 50;
+    int version = clazz.hasClassFileVersion() ? clazz.getInitialClassFileVersion() : V1_6;
     for (DexEncodedMethod method : clazz.directMethods()) {
       version = Math.max(version, getClassFileVersion(method));
     }
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 332477a..2dc287a 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -10,9 +10,7 @@
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexReference;
@@ -20,6 +18,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue.DexItemBasedValueString;
 import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstString;
@@ -92,12 +91,11 @@
     }
   }
 
-  public void decoupleIdentifierNameStringsInMethod(DexEncodedMethod method, IRCode code) {
-    decoupleIdentifierNameStringsInBlocks(method, code, null);
+  public void decoupleIdentifierNameStringsInMethod(IRCode code) {
+    decoupleIdentifierNameStringsInBlocks(code, null);
   }
 
-  public void decoupleIdentifierNameStringsInBlocks(
-      DexEncodedMethod method, IRCode code, Set<BasicBlock> blocks) {
+  public void decoupleIdentifierNameStringsInBlocks(IRCode code, Set<BasicBlock> blocks) {
     if (!code.metadata().mayHaveConstString()) {
       return;
     }
@@ -124,11 +122,11 @@
         if (instruction.isStaticPut() || instruction.isInstancePut()) {
           iterator =
               decoupleIdentifierNameStringForFieldPutInstruction(
-                  code, method, blockIterator, iterator, instruction.asFieldInstruction());
+                  code, blockIterator, iterator, instruction.asFieldInstruction());
         } else if (instruction.isInvokeMethod()) {
           iterator =
               decoupleIdentifierNameStringForInvokeInstruction(
-                  code, method, blockIterator, iterator, instruction.asInvokeMethod());
+                  code, blockIterator, iterator, instruction.asInvokeMethod());
         }
       }
     }
@@ -136,7 +134,6 @@
 
   private InstructionListIterator decoupleIdentifierNameStringForFieldPutInstruction(
       IRCode code,
-      DexEncodedMethod method,
       ListIterator<BasicBlock> blocks,
       InstructionListIterator iterator,
       FieldInstruction instruction) {
@@ -148,13 +145,13 @@
     }
     Value in = instruction.value();
     if (!in.isConstString()) {
-      warnUndeterminedIdentifierIfNecessary(field, method.holder(), instruction, null);
+      warnUndeterminedIdentifierIfNecessary(field, code.context(), instruction, null);
       return iterator;
     }
     DexString original = in.getConstInstruction().asConstString().getValue();
     DexReference itemBasedString = inferMemberOrTypeFromNameString(appView, original);
     if (itemBasedString == null) {
-      warnUndeterminedIdentifierIfNecessary(field, method.holder(), instruction, original);
+      warnUndeterminedIdentifierIfNecessary(field, code.context(), instruction, original);
       return iterator;
     }
     // Move the cursor back to $fieldPut
@@ -199,7 +196,6 @@
 
   private InstructionListIterator decoupleIdentifierNameStringForInvokeInstruction(
       IRCode code,
-      DexEncodedMethod method,
       ListIterator<BasicBlock> blocks,
       InstructionListIterator iterator,
       InvokeMethod invoke) {
@@ -213,8 +209,7 @@
     if (isReflectionMethod(appView.dexItemFactory(), invokedMethod) || isClassNameComparison) {
       DexReference itemBasedString = identifyIdentifier(invoke, appView);
       if (itemBasedString == null) {
-        DexType context = method.holder();
-        warnUndeterminedIdentifierIfNecessary(invokedMethod, context, invoke, null);
+        warnUndeterminedIdentifierIfNecessary(invokedMethod, code.context(), invoke, null);
         return iterator;
       }
 
@@ -280,13 +275,13 @@
       for (int i = 0; i < ins.size(); i++) {
         Value in = ins.get(i);
         if (!in.isConstString()) {
-          warnUndeterminedIdentifierIfNecessary(invokedMethod, method.holder(), invoke, null);
+          warnUndeterminedIdentifierIfNecessary(invokedMethod, code.context(), invoke, null);
           continue;
         }
         DexString original = in.getConstInstruction().asConstString().getValue();
         DexReference itemBasedString = inferMemberOrTypeFromNameString(appView, original);
         if (itemBasedString == null) {
-          warnUndeterminedIdentifierIfNecessary(invokedMethod, method.holder(), invoke, original);
+          warnUndeterminedIdentifierIfNecessary(invokedMethod, code.context(), invoke, original);
           continue;
         }
         // Move the cursor back to $invoke
@@ -361,23 +356,18 @@
   }
 
   private void warnUndeterminedIdentifierIfNecessary(
-      DexReference member, DexType originHolder, Instruction instruction, DexString original) {
+      DexReference member, ProgramMethod method, Instruction instruction, DexString original) {
     assert member.isDexField() || member.isDexMethod();
     // Only issue warnings for -identifiernamestring rules explicitly added by the user.
     boolean matchedByExplicitRule = identifierNameStrings.getBoolean(member);
     if (!matchedByExplicitRule) {
       return;
     }
-    DexClass originClass = appView.definitionFor(originHolder);
-    // If the origin is a library class, it is out of developers' control.
-    if (originClass != null && originClass.isNotProgramClass()) {
-      return;
-    }
     // Undetermined identifiers matter only if minification is enabled.
     if (!appView.options().isMinifying()) {
       return;
     }
-    Origin origin = appView.appInfo().originFor(originHolder);
+    Origin origin = method.getOrigin();
     String kind = member.isDexField() ? "field" : "method";
     String originalMessage = original == null ? "what identifier string flows to "
         : "what '" + original.toString() + "' refers to, which flows to ";
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 d37ef84..45892a2 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -7,12 +7,13 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ForEachable;
+import com.android.tools.r8.utils.IntBox;
 import com.google.common.collect.Sets;
-import java.util.List;
 import java.util.Set;
 
 public class VisibilityBridgeRemover {
@@ -24,52 +25,61 @@
   }
 
   private void removeUnneededVisibilityBridgesFromClass(DexProgramClass clazz) {
-    DexEncodedMethod[] newDirectMethods = removeUnneededVisibilityBridges(clazz.directMethods());
+    DexEncodedMethod[] newDirectMethods =
+        removeUnneededVisibilityBridges(
+            clazz::forEachProgramDirectMethod, clazz.getMethodCollection().numberOfDirectMethods());
     if (newDirectMethods != null) {
       clazz.setDirectMethods(newDirectMethods);
     }
-    DexEncodedMethod[] newVirtualMethods = removeUnneededVisibilityBridges(clazz.virtualMethods());
+    DexEncodedMethod[] newVirtualMethods =
+        removeUnneededVisibilityBridges(
+            clazz::forEachProgramVirtualMethod,
+            clazz.getMethodCollection().numberOfVirtualMethods());
     if (newVirtualMethods != null) {
       clazz.setVirtualMethods(newVirtualMethods);
     }
   }
 
-  private DexEncodedMethod[] removeUnneededVisibilityBridges(List<DexEncodedMethod> methods) {
-    Set<DexEncodedMethod> methodsToBeRemoved = null;
-    for (DexEncodedMethod method : methods) {
-      if (isUnneededVisibilityBridge(method)) {
-        if (methodsToBeRemoved == null) {
-          methodsToBeRemoved = Sets.newIdentityHashSet();
-        }
-        methodsToBeRemoved.add(method);
-      }
-    }
-    if (methodsToBeRemoved != null) {
-      Set<DexEncodedMethod> finalMethodsToBeRemoved = methodsToBeRemoved;
-      return methods.stream()
-          .filter(method -> !finalMethodsToBeRemoved.contains(method))
-          .toArray(DexEncodedMethod[]::new);
+  private DexEncodedMethod[] removeUnneededVisibilityBridges(
+      ForEachable<ProgramMethod> methods, int size) {
+    Set<DexEncodedMethod> methodsToBeRemoved = Sets.newIdentityHashSet();
+    methods.forEach(
+        method -> {
+          if (isUnneededVisibilityBridge(method)) {
+            methodsToBeRemoved.add(method.getDefinition());
+          }
+        });
+    if (!methodsToBeRemoved.isEmpty()) {
+      DexEncodedMethod[] newMethods = new DexEncodedMethod[size - methodsToBeRemoved.size()];
+      IntBox i = new IntBox(0);
+      methods.forEach(
+          method -> {
+            if (!methodsToBeRemoved.contains(method.getDefinition())) {
+              newMethods[i.getAndIncrement()] = method.getDefinition();
+            }
+          });
+      return newMethods;
     }
     return null;
   }
 
-  private boolean isUnneededVisibilityBridge(DexEncodedMethod method) {
-    if (appView.appInfo().isPinned(method.method)) {
+  private boolean isUnneededVisibilityBridge(ProgramMethod method) {
+    if (appView.appInfo().isPinned(method.getReference())) {
       return false;
     }
-    MethodAccessFlags accessFlags = method.accessFlags;
-    if (!accessFlags.isBridge() || accessFlags.isAbstract()) {
+    DexEncodedMethod definition = method.getDefinition();
+    if (!definition.isBridge() || definition.isAbstract()) {
       return false;
     }
     InvokeSingleTargetExtractor targetExtractor =
         new InvokeSingleTargetExtractor(appView.dexItemFactory());
-    method.getCode().registerCodeReferences(method, targetExtractor);
+    method.registerCodeReferences(targetExtractor);
     DexMethod target = targetExtractor.getTarget();
     InvokeKind kind = targetExtractor.getKind();
     // javac-generated visibility forward bridge method has same descriptor (name, signature and
     // return type).
-    if (target != null && target.hasSameProtoAndName(method.method)) {
-      assert !accessFlags.isPrivate() && !accessFlags.isConstructor();
+    if (target != null && target.hasSameProtoAndName(method.getReference())) {
+      assert !definition.isPrivate() && !definition.isInstanceInitializer();
       if (kind == InvokeKind.SUPER) {
         // This is a visibility forward, so check for the direct target.
         DexEncodedMethod targetMethod =
@@ -77,10 +87,7 @@
         if (targetMethod != null && targetMethod.accessFlags.isPublic()) {
           if (Log.ENABLED) {
             Log.info(
-                getClass(),
-                "Removing visibility forwarding %s -> %s",
-                method.method,
-                targetMethod.method);
+                getClass(), "Removing visibility forwarding %s -> %s", method, targetMethod.method);
           }
           return true;
         }
diff --git a/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java b/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java
index c2f5837..3ec10a2 100644
--- a/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java
+++ b/src/main/java/com/android/tools/r8/relocator/SimplePackagesRewritingMapper.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -35,6 +36,15 @@
   }
 
   public NamingLens compute(Map<PackageReference, PackageReference> mapping) {
+    // Prefetch all code objects to ensure we have seen all types.
+    // TODO(b/129925954): When updated, there is no need for this prefetch.
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      for (DexEncodedMethod method : clazz.methods()) {
+        if (method.getCode() != null) {
+          method.getCode().asCfCode();
+        }
+      }
+    }
     ImmutableMap.Builder<String, String> packingMappings = ImmutableMap.builder();
     for (PackageReference key : mapping.keySet()) {
       String source = key.getPackageName();
@@ -52,32 +62,36 @@
       packingMappings.put(sourceBinary, targetBinary);
       DexString sourceDescriptor = appView.dexItemFactory().createString("L" + sourceBinary);
       DexString targetDescriptor = appView.dexItemFactory().createString("L" + targetBinary);
-      for (DexProgramClass clazz : appView.appInfo().classes()) {
-        DexString descriptor = clazz.type.descriptor;
-        // Check if descriptor can be a prefix.
-        if (descriptor.size <= sourceDescriptor.size) {
-          continue;
-        }
-        // Check if it is either the empty prefix or a fully qualified package.
-        if (sourceDescriptor.size != 1
-            && descriptor.content[sourceDescriptor.size]
-                != DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR) {
-          continue;
-        }
-        // Do a char-by-char comparison of the prefix.
-        if (!descriptor.startsWith(sourceDescriptor)) {
-          continue;
-        }
-        // This type should be mapped.
-        if (typeMappings.containsKey(clazz.type)) {
-          appView.options().reporter.error(RelocatorDiagnostic.typeRelocateAmbiguous(clazz.type));
-          appView.options().reporter.failIfPendingErrors();
-        }
-        DexString relocatedDescriptor =
-            clazz.type.descriptor.withNewPrefix(
-                sourceDescriptor, targetDescriptor, appView.dexItemFactory());
-        typeMappings.put(clazz.type, relocatedDescriptor);
-      }
+      // TODO(b/129925954): Change to a lazy implementation in the naming lens.
+      appView
+          .dexItemFactory()
+          .forAllTypes(
+              type -> {
+                DexString descriptor = type.descriptor;
+                // Check if descriptor can be a prefix.
+                if (descriptor.size <= sourceDescriptor.size) {
+                  return;
+                }
+                // Check if it is either the empty prefix or a fully qualified package.
+                if (sourceDescriptor.size != 1
+                    && descriptor.content[sourceDescriptor.size]
+                        != DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR) {
+                  return;
+                }
+                // Do a char-by-char comparison of the prefix.
+                if (!descriptor.startsWith(sourceDescriptor)) {
+                  return;
+                }
+                // This type should be mapped.
+                if (typeMappings.containsKey(type)) {
+                  appView.options().reporter.error(RelocatorDiagnostic.typeRelocateAmbiguous(type));
+                  appView.options().reporter.failIfPendingErrors();
+                }
+                DexString relocatedDescriptor =
+                    type.descriptor.withNewPrefix(
+                        sourceDescriptor, targetDescriptor, appView.dexItemFactory());
+                typeMappings.put(type, relocatedDescriptor);
+              });
     }
 
     return new RelocatorNamingLens(typeMappings, packingMappings.build(), appView.dexItemFactory());
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 3f657b9..77edcc6 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.graph.DexEncodedMethod.asProgramMethodOrNull;
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.graph.GraphLense.rewriteReferenceKeys;
 import static com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult.isOverriding;
@@ -34,6 +35,7 @@
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
 import com.android.tools.r8.graph.PresortedComparable;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -1074,6 +1076,15 @@
     }
   }
 
+  public ProgramMethod lookupSingleProgramTarget(
+      Type type,
+      DexMethod target,
+      DexType invocationContext,
+      LibraryModeledPredicate modeledPredicate) {
+    return asProgramMethodOrNull(
+        lookupSingleTarget(type, target, invocationContext, modeledPredicate), this);
+  }
+
   /** For mapping invoke virtual instruction to single target method. */
   public DexEncodedMethod lookupSingleVirtualTarget(
       DexMethod method, DexType invocationContext, boolean isInterface) {
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index 09aafdb..7c0afe9 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -20,10 +20,9 @@
   private final ProgramMethod context;
   protected final Enqueuer enqueuer;
 
-  public DefaultEnqueuerUseRegistry(
-      AppView<?> appView, DexProgramClass holder, DexEncodedMethod method, Enqueuer enqueuer) {
+  public DefaultEnqueuerUseRegistry(AppView<?> appView, ProgramMethod context, Enqueuer enqueuer) {
     super(appView.dexItemFactory());
-    this.context = new ProgramMethod(holder, method);
+    this.context = context;
     this.enqueuer = enqueuer;
   }
 
@@ -71,22 +70,22 @@
 
   @Override
   public boolean registerInstanceFieldRead(DexField field) {
-    return enqueuer.traceInstanceFieldRead(field, context.getDefinition());
+    return enqueuer.traceInstanceFieldRead(field, context);
   }
 
   @Override
   public boolean registerInstanceFieldReadFromMethodHandle(DexField field) {
-    return enqueuer.traceInstanceFieldReadFromMethodHandle(field, context.getDefinition());
+    return enqueuer.traceInstanceFieldReadFromMethodHandle(field, context);
   }
 
   @Override
   public boolean registerInstanceFieldWrite(DexField field) {
-    return enqueuer.traceInstanceFieldWrite(field, context.getDefinition());
+    return enqueuer.traceInstanceFieldWrite(field, context);
   }
 
   @Override
   public boolean registerInstanceFieldWriteFromMethodHandle(DexField field) {
-    return enqueuer.traceInstanceFieldWriteFromMethodHandle(field, context.getDefinition());
+    return enqueuer.traceInstanceFieldWriteFromMethodHandle(field, context);
   }
 
   @Override
@@ -96,43 +95,43 @@
 
   @Override
   public boolean registerStaticFieldRead(DexField field) {
-    return enqueuer.traceStaticFieldRead(field, context.getDefinition());
+    return enqueuer.traceStaticFieldRead(field, context);
   }
 
   @Override
   public boolean registerStaticFieldReadFromMethodHandle(DexField field) {
-    return enqueuer.traceStaticFieldReadFromMethodHandle(field, context.getDefinition());
+    return enqueuer.traceStaticFieldReadFromMethodHandle(field, context);
   }
 
   @Override
   public boolean registerStaticFieldWrite(DexField field) {
-    return enqueuer.traceStaticFieldWrite(field, context.getDefinition());
+    return enqueuer.traceStaticFieldWrite(field, context);
   }
 
   @Override
   public boolean registerStaticFieldWriteFromMethodHandle(DexField field) {
-    return enqueuer.traceStaticFieldWriteFromMethodHandle(field, context.getDefinition());
+    return enqueuer.traceStaticFieldWriteFromMethodHandle(field, context);
   }
 
   @Override
   public boolean registerConstClass(DexType type) {
-    return enqueuer.traceConstClass(type, context.getDefinition());
+    return enqueuer.traceConstClass(type, context);
   }
 
   @Override
   public boolean registerCheckCast(DexType type) {
-    return enqueuer.traceCheckCast(type, context.getDefinition());
+    return enqueuer.traceCheckCast(type, context);
   }
 
   @Override
   public boolean registerTypeReference(DexType type) {
-    return enqueuer.traceTypeReference(type, context.getDefinition());
+    return enqueuer.traceTypeReference(type, context);
   }
 
   @Override
   public void registerMethodHandle(DexMethodHandle methodHandle, MethodHandleUse use) {
     super.registerMethodHandle(methodHandle, use);
-    enqueuer.traceMethodHandle(methodHandle, use, context.getDefinition());
+    enqueuer.traceMethodHandle(methodHandle, use, context);
   }
 
   @Override
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 a1d6312..9604389 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -85,7 +85,6 @@
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
 import com.android.tools.r8.shaking.EnqueuerWorklist.EnqueuerAction;
 import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
@@ -104,6 +103,7 @@
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.Visibility;
 import com.android.tools.r8.utils.WorkList;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
@@ -201,8 +201,8 @@
    * Tracks the dependency between a method and the super-method it calls, if any. Used to make
    * super methods become live when they become reachable from a live sub-method.
    */
-  private final Map<DexEncodedMethod, Set<DexEncodedMethod>> superInvokeDependencies = Maps
-      .newIdentityHashMap();
+  private final Map<DexEncodedMethod, ProgramMethodSet> superInvokeDependencies =
+      Maps.newIdentityHashMap();
   /** Set of instance fields that can be reached by read/write operations. */
   private final Map<DexProgramClass, SetWithReason<DexEncodedField>> reachableInstanceFields =
       Maps.newIdentityHashMap();
@@ -283,10 +283,8 @@
   /** A queue of items that need processing. Different items trigger different actions. */
   private final EnqueuerWorklist workList;
 
-  /**
-   * A set of methods that need code inspection for Java reflection in use.
-   */
-  private final Set<DexEncodedMethod> pendingReflectiveUses = Sets.newLinkedHashSet();
+  /** A set of methods that need code inspection for Java reflection in use. */
+  private final ProgramMethodSet pendingReflectiveUses = ProgramMethodSet.createLinked();
 
   /** Mapping of types to the methods reachable at that type. */
   private final Map<DexProgramClass, Set<DexMethod>> reachableVirtualTargets =
@@ -336,7 +334,7 @@
 
   private final LambdaRewriter lambdaRewriter;
   private final DesugaredLibraryConversionWrapperAnalysis desugaredLibraryWrapperAnalysis;
-  private final Map<DexType, Pair<LambdaClass, DexEncodedMethod>> lambdaClasses =
+  private final Map<DexType, Pair<LambdaClass, ProgramMethod>> lambdaClasses =
       new IdentityHashMap<>();
   private final Map<DexEncodedMethod, Map<DexCallSite, LambdaClass>> lambdaCallSites =
       new IdentityHashMap<>();
@@ -600,15 +598,14 @@
       } else {
         workList.enqueueMarkInstantiatedAction(clazz, null, InstantiationReason.KEEP_RULE, witness);
         if (clazz.hasDefaultInitializer()) {
-          DexEncodedMethod defaultInitializer = clazz.getDefaultInitializer();
+          ProgramMethod defaultInitializer = clazz.getProgramDefaultInitializer();
           if (forceProguardCompatibility) {
             workList.enqueueMarkMethodKeptAction(
-                clazz,
                 defaultInitializer,
-                graphReporter.reportCompatKeepDefaultInitializer(clazz, defaultInitializer));
+                graphReporter.reportCompatKeepDefaultInitializer(defaultInitializer));
           }
           if (clazz.isExternalizable(appView)) {
-            enqueueMarkMethodLiveAction(clazz, defaultInitializer, witness);
+            enqueueMarkMethodLiveAction(defaultInitializer, witness);
           }
         }
       }
@@ -626,8 +623,7 @@
       DexProgramClass holder = getProgramClassOrNull(encodedMethod.holder());
       if (holder != null) {
         workList.enqueueMarkMethodKeptAction(
-            holder,
-            encodedMethod,
+            new ProgramMethod(holder, encodedMethod),
             graphReporter.reportKeepMethod(precondition, rules, encodedMethod));
       }
     } else {
@@ -649,16 +645,14 @@
       clazz = superClass;
     }
     if (clazz.hasDefaultInitializer()) {
-      enqueueMarkMethodLiveAction(clazz, clazz.getDefaultInitializer(), reason);
+      enqueueMarkMethodLiveAction(clazz.getProgramDefaultInitializer(), reason);
     }
   }
 
   // Utility to avoid adding to the worklist if already live.
-  private boolean enqueueMarkMethodLiveAction(
-      DexProgramClass clazz, DexEncodedMethod method, KeepReason reason) {
-    assert method.holder() == clazz.type;
-    if (liveMethods.add(clazz, method, reason)) {
-      workList.enqueueMarkMethodLiveAction(clazz, method, reason);
+  private boolean enqueueMarkMethodLiveAction(ProgramMethod method, KeepReason reason) {
+    if (liveMethods.add(method, reason)) {
+      workList.enqueueMarkMethodLiveAction(method, reason);
       return true;
     }
     return false;
@@ -678,34 +672,39 @@
   //
 
   private boolean registerMethodWithTargetAndContext(
-      Map<DexMethod, Set<DexEncodedMethod>> seen, DexMethod method, DexEncodedMethod context) {
+      Map<DexMethod, Set<DexEncodedMethod>> seen, DexMethod method, ProgramMethod context) {
     DexType baseHolder = method.holder.toBaseType(appView.dexItemFactory());
     if (baseHolder.isClassType()) {
       markTypeAsLive(baseHolder, clazz -> graphReporter.reportClassReferencedFrom(clazz, context));
-      return seen.computeIfAbsent(method, ignore -> Sets.newIdentityHashSet()).add(context);
+      return seen.computeIfAbsent(method, ignore -> Sets.newIdentityHashSet())
+          .add(context.getDefinition());
     }
     return false;
   }
 
-  public boolean registerFieldRead(DexField field, DexEncodedMethod context) {
-    return registerFieldAccess(field, context, true, false);
+  public boolean registerFieldRead(DexField field, ProgramMethod context) {
+    return registerFieldAccess(field, context.getDefinition(), true, false);
   }
 
-  public boolean registerReflectiveFieldRead(DexField field, DexEncodedMethod context) {
-    return registerFieldAccess(field, context, true, true);
+  public boolean registerFieldReadFromAnnotation(DexField field) {
+    return registerFieldAccess(field, DexEncodedMethod.ANNOTATION_REFERENCE, true, false);
   }
 
-  public boolean registerFieldWrite(DexField field, DexEncodedMethod context) {
-    return registerFieldAccess(field, context, false, false);
+  public boolean registerReflectiveFieldRead(DexField field, ProgramMethod context) {
+    return registerFieldAccess(field, context.getDefinition(), true, true);
   }
 
-  public boolean registerReflectiveFieldWrite(DexField field, DexEncodedMethod context) {
-    return registerFieldAccess(field, context, false, true);
+  public boolean registerFieldWrite(DexField field, ProgramMethod context) {
+    return registerFieldAccess(field, context.getDefinition(), false, false);
   }
 
-  public boolean registerReflectiveFieldAccess(DexField field, DexEncodedMethod context) {
-    boolean changed = registerFieldAccess(field, context, true, true);
-    changed |= registerFieldAccess(field, context, false, true);
+  public boolean registerReflectiveFieldWrite(DexField field, ProgramMethod context) {
+    return registerFieldAccess(field, context.getDefinition(), false, true);
+  }
+
+  public boolean registerReflectiveFieldAccess(DexField field, ProgramMethod context) {
+    boolean changed = registerFieldAccess(field, context.getDefinition(), true, true);
+    changed |= registerFieldAccess(field, context.getDefinition(), false, true);
     return changed;
   }
 
@@ -745,12 +744,6 @@
     return isRead ? info.recordRead(field, context) : info.recordWrite(field, context);
   }
 
-  private boolean isStringConcat(DexMethodHandle bootstrapMethod) {
-    return bootstrapMethod.type.isInvokeStatic()
-        && (bootstrapMethod.asMethod() == appView.dexItemFactory().stringConcatWithConstantsMethod
-            || bootstrapMethod.asMethod() == appView.dexItemFactory().stringConcatMethod);
-  }
-
   void traceCallSite(DexCallSite callSite, ProgramMethod context) {
     DexProgramClass bootstrapClass =
         getProgramClassOrNull(callSite.bootstrapMethod.asMethod().holder);
@@ -771,7 +764,7 @@
       if (code != null) {
         LambdaClass lambdaClass =
             lambdaRewriter.getOrCreateLambdaClass(descriptor, contextMethod.holder());
-        lambdaClasses.put(lambdaClass.type, new Pair<>(lambdaClass, contextMethod));
+        lambdaClasses.put(lambdaClass.type, new Pair<>(lambdaClass, context));
         lambdaCallSites
             .computeIfAbsent(contextMethod, k -> new IdentityHashMap<>())
             .put(callSite, lambdaClass);
@@ -783,7 +776,7 @@
         desugaredLambdaImplementationMethods.add(descriptor.implHandle.asMethod());
       }
     } else {
-      markLambdaAsInstantiated(descriptor, contextMethod);
+      markLambdaAsInstantiated(descriptor, context);
       transitionMethodsForInstantiatedLambda(descriptor);
       callSites.add(callSite);
     }
@@ -820,11 +813,11 @@
     }
   }
 
-  boolean traceCheckCast(DexType type, DexEncodedMethod currentMethod) {
+  boolean traceCheckCast(DexType type, ProgramMethod currentMethod) {
     return traceConstClassOrCheckCast(type, currentMethod);
   }
 
-  boolean traceConstClass(DexType type, DexEncodedMethod currentMethod) {
+  boolean traceConstClass(DexType type, ProgramMethod currentMethod) {
     // We conservatively group T.class and T[].class to ensure that we do not merge T with S if
     // potential locks on T[].class and S[].class exists.
     DexType baseType = type.toBaseType(appView.dexItemFactory());
@@ -837,7 +830,7 @@
     return traceConstClassOrCheckCast(type, currentMethod);
   }
 
-  private boolean traceConstClassOrCheckCast(DexType type, DexEncodedMethod currentMethod) {
+  private boolean traceConstClassOrCheckCast(DexType type, ProgramMethod currentMethod) {
     if (!forceProguardCompatibility) {
       return traceTypeReference(type, currentMethod);
     }
@@ -868,7 +861,7 @@
       initClassReferences.put(
           type, computeMinimumRequiredVisibilityForInitClassField(type, currentMethod.getHolder()));
 
-      markTypeAsLive(type, classReferencedFromReporter(currentMethod.getDefinition()));
+      markTypeAsLive(type, classReferencedFromReporter(currentMethod));
       markDirectAndIndirectClassInitializersAsLive(clazz);
       return true;
     }
@@ -914,7 +907,7 @@
   }
 
   void traceMethodHandle(
-      DexMethodHandle methodHandle, MethodHandleUse use, DexEncodedMethod currentMethod) {
+      DexMethodHandle methodHandle, MethodHandleUse use, ProgramMethod currentMethod) {
     // If a method handle is not an argument to a lambda metafactory it could flow to a
     // MethodHandle.invokeExact invocation. For that to work, the receiver type cannot have
     // changed and therefore we cannot perform member rebinding. For these handles, we maintain
@@ -938,33 +931,28 @@
     }
   }
 
-  boolean traceTypeReference(DexType type, DexEncodedMethod currentMethod) {
+  boolean traceTypeReference(DexType type, ProgramMethod currentMethod) {
     markTypeAsLive(type, classReferencedFromReporter(currentMethod));
     return true;
   }
 
   boolean traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context) {
-    DexProgramClass currentHolder = context.getHolder();
-    DexEncodedMethod currentMethod = context.getDefinition();
     boolean skipTracing =
         registerDeferredActionForDeadProtoBuilder(
             invokedMethod.holder,
-            currentMethod,
-            () ->
-                workList.enqueueTraceInvokeDirectAction(
-                    invokedMethod, currentHolder, currentMethod));
+            context,
+            () -> workList.enqueueTraceInvokeDirectAction(invokedMethod, context));
     if (skipTracing) {
       addDeadProtoTypeCandidate(invokedMethod.holder);
       return false;
     }
 
-    return traceInvokeDirect(
-        invokedMethod, context, KeepReason.invokedFrom(currentHolder, currentMethod));
+    return traceInvokeDirect(invokedMethod, context, KeepReason.invokedFrom(context));
   }
 
   /** Returns true if a deferred action was registered. */
   private boolean registerDeferredActionForDeadProtoBuilder(
-      DexType type, DexEncodedMethod currentMethod, Action action) {
+      DexType type, ProgramMethod currentMethod, Action action) {
     DexProgramClass clazz = getProgramClassOrNull(type);
     if (clazz != null) {
       return appView.withGeneratedMessageLiteBuilderShrinker(
@@ -978,13 +966,12 @@
 
   boolean traceInvokeDirectFromLambda(DexMethod invokedMethod, ProgramMethod context) {
     return traceInvokeDirect(
-        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getDefinition()));
+        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context));
   }
 
   private boolean traceInvokeDirect(
       DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
-    DexEncodedMethod currentMethod = context.getDefinition();
-    if (!registerMethodWithTargetAndContext(directInvokes, invokedMethod, currentMethod)) {
+    if (!registerMethodWithTargetAndContext(directInvokes, invokedMethod, context)) {
       return false;
     }
     if (Log.ENABLED) {
@@ -1001,13 +988,12 @@
 
   boolean traceInvokeInterfaceFromLambda(DexMethod invokedMethod, ProgramMethod context) {
     return traceInvokeInterface(
-        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getDefinition()));
+        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context));
   }
 
   private boolean traceInvokeInterface(
       DexMethod method, ProgramMethod context, KeepReason keepReason) {
-    DexEncodedMethod currentMethod = context.getDefinition();
-    if (!registerMethodWithTargetAndContext(interfaceInvokes, method, currentMethod)) {
+    if (!registerMethodWithTargetAndContext(interfaceInvokes, method, context)) {
       return false;
     }
     if (Log.ENABLED) {
@@ -1024,32 +1010,31 @@
 
   boolean traceInvokeStaticFromLambda(DexMethod invokedMethod, ProgramMethod context) {
     return traceInvokeStatic(
-        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getDefinition()));
+        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context));
   }
 
   private boolean traceInvokeStatic(
       DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
-    DexEncodedMethod currentMethod = context.getDefinition();
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     if (dexItemFactory.classMethods.isReflectiveClassLookup(invokedMethod)
         || dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(invokedMethod)) {
       // Implicitly add -identifiernamestring rule for the Java reflection in use.
       identifierNameStrings.add(invokedMethod);
       // Revisit the current method to implicitly add -keep rule for items with reflective access.
-      pendingReflectiveUses.add(currentMethod);
+      pendingReflectiveUses.add(context);
     }
     // See comment in handleJavaLangEnumValueOf.
     if (invokedMethod == dexItemFactory.enumMethods.valueOf) {
-      pendingReflectiveUses.add(currentMethod);
+      pendingReflectiveUses.add(context);
     }
     // Handling of application services.
     if (dexItemFactory.serviceLoaderMethods.isLoadMethod(invokedMethod)) {
-      pendingReflectiveUses.add(currentMethod);
+      pendingReflectiveUses.add(context);
     }
     if (invokedMethod == dexItemFactory.proxyMethods.newProxyInstance) {
-      pendingReflectiveUses.add(currentMethod);
+      pendingReflectiveUses.add(context);
     }
-    if (!registerMethodWithTargetAndContext(staticInvokes, invokedMethod, currentMethod)) {
+    if (!registerMethodWithTargetAndContext(staticInvokes, invokedMethod, context)) {
       return false;
     }
     if (Log.ENABLED) {
@@ -1061,17 +1046,16 @@
   }
 
   boolean traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context) {
-    DexEncodedMethod currentMethod = context.getDefinition();
     // We have to revisit super invokes based on the context they are found in. The same
     // method descriptor will hit different targets, depending on the context it is used in.
-    DexMethod actualTarget = getInvokeSuperTarget(invokedMethod, currentMethod);
-    if (!registerMethodWithTargetAndContext(superInvokes, invokedMethod, currentMethod)) {
+    DexMethod actualTarget = getInvokeSuperTarget(invokedMethod, context);
+    if (!registerMethodWithTargetAndContext(superInvokes, invokedMethod, context)) {
       return false;
     }
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Register invokeSuper `%s`.", actualTarget);
     }
-    workList.enqueueMarkReachableSuperAction(invokedMethod, currentMethod);
+    workList.enqueueMarkReachableSuperAction(invokedMethod, context);
     invokeAnalyses.forEach(analysis -> analysis.traceInvokeSuper(invokedMethod, context));
     return true;
   }
@@ -1082,22 +1066,21 @@
 
   boolean traceInvokeVirtualFromLambda(DexMethod invokedMethod, ProgramMethod context) {
     return traceInvokeVirtual(
-        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context.getDefinition()));
+        invokedMethod, context, KeepReason.invokedFromLambdaCreatedIn(context));
   }
 
   private boolean traceInvokeVirtual(
       DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
     if (invokedMethod == appView.dexItemFactory().classMethods.newInstance
         || invokedMethod == appView.dexItemFactory().constructorMethods.newInstance) {
-      pendingReflectiveUses.add(context.getDefinition());
+      pendingReflectiveUses.add(context);
     } else if (appView.dexItemFactory().classMethods.isReflectiveMemberLookup(invokedMethod)) {
       // Implicitly add -identifiernamestring rule for the Java reflection in use.
       identifierNameStrings.add(invokedMethod);
       // Revisit the current method to implicitly add -keep rule for items with reflective access.
-      pendingReflectiveUses.add(context.getDefinition());
+      pendingReflectiveUses.add(context);
     }
-    if (!registerMethodWithTargetAndContext(
-        virtualInvokes, invokedMethod, context.getDefinition())) {
+    if (!registerMethodWithTargetAndContext(virtualInvokes, invokedMethod, context)) {
       return false;
     }
     if (Log.ENABLED) {
@@ -1109,10 +1092,9 @@
   }
 
   boolean traceNewInstance(DexType type, ProgramMethod context) {
-    DexEncodedMethod currentMethod = context.getDefinition();
     boolean skipTracing =
         registerDeferredActionForDeadProtoBuilder(
-            type, currentMethod, () -> workList.enqueueTraceNewInstanceAction(type, context));
+            type, context, () -> workList.enqueueTraceNewInstanceAction(type, context));
     if (skipTracing) {
       addDeadProtoTypeCandidate(type);
       return false;
@@ -1122,15 +1104,12 @@
         type,
         context,
         InstantiationReason.NEW_INSTANCE_INSTRUCTION,
-        KeepReason.instantiatedIn(currentMethod));
+        KeepReason.instantiatedIn(context));
   }
 
   boolean traceNewInstanceFromLambda(DexType type, ProgramMethod context) {
     return traceNewInstance(
-        type,
-        context,
-        InstantiationReason.LAMBDA,
-        KeepReason.invokedFromLambdaCreatedIn(context.getDefinition()));
+        type, context, InstantiationReason.LAMBDA, KeepReason.invokedFromLambdaCreatedIn(context));
   }
 
   private boolean traceNewInstance(
@@ -1138,29 +1117,27 @@
       ProgramMethod context,
       InstantiationReason instantiationReason,
       KeepReason keepReason) {
-    DexEncodedMethod currentMethod = context.getDefinition();
     DexProgramClass clazz = getProgramClassOrNull(type);
     if (clazz != null) {
       if (clazz.isAnnotation() || clazz.isInterface()) {
         markTypeAsLive(clazz, graphReporter.registerClass(clazz, keepReason));
       } else {
-        workList.enqueueMarkInstantiatedAction(
-            clazz, currentMethod, instantiationReason, keepReason);
+        workList.enqueueMarkInstantiatedAction(clazz, context, instantiationReason, keepReason);
       }
     }
     return true;
   }
 
-  boolean traceInstanceFieldRead(DexField field, DexEncodedMethod currentMethod) {
+  boolean traceInstanceFieldRead(DexField field, ProgramMethod currentMethod) {
     return traceInstanceFieldRead(field, currentMethod, false);
   }
 
-  boolean traceInstanceFieldReadFromMethodHandle(DexField field, DexEncodedMethod currentMethod) {
+  boolean traceInstanceFieldReadFromMethodHandle(DexField field, ProgramMethod currentMethod) {
     return traceInstanceFieldRead(field, currentMethod, true);
   }
 
   private boolean traceInstanceFieldRead(
-      DexField field, DexEncodedMethod currentMethod, boolean fromMethodHandle) {
+      DexField field, ProgramMethod currentMethod, boolean fromMethodHandle) {
     if (!registerFieldRead(field, currentMethod)) {
       return false;
     }
@@ -1200,16 +1177,16 @@
     return true;
   }
 
-  boolean traceInstanceFieldWrite(DexField field, DexEncodedMethod currentMethod) {
+  boolean traceInstanceFieldWrite(DexField field, ProgramMethod currentMethod) {
     return traceInstanceFieldWrite(field, currentMethod, false);
   }
 
-  boolean traceInstanceFieldWriteFromMethodHandle(DexField field, DexEncodedMethod currentMethod) {
+  boolean traceInstanceFieldWriteFromMethodHandle(DexField field, ProgramMethod currentMethod) {
     return traceInstanceFieldWrite(field, currentMethod, true);
   }
 
   private boolean traceInstanceFieldWrite(
-      DexField field, DexEncodedMethod currentMethod, boolean fromMethodHandle) {
+      DexField field, ProgramMethod currentMethod, boolean fromMethodHandle) {
     if (!registerFieldWrite(field, currentMethod)) {
       return false;
     }
@@ -1249,16 +1226,16 @@
     return true;
   }
 
-  boolean traceStaticFieldRead(DexField field, DexEncodedMethod currentMethod) {
+  boolean traceStaticFieldRead(DexField field, ProgramMethod currentMethod) {
     return traceStaticFieldRead(field, currentMethod, false);
   }
 
-  boolean traceStaticFieldReadFromMethodHandle(DexField field, DexEncodedMethod currentMethod) {
+  boolean traceStaticFieldReadFromMethodHandle(DexField field, ProgramMethod currentMethod) {
     return traceStaticFieldRead(field, currentMethod, true);
   }
 
   private boolean traceStaticFieldRead(
-      DexField field, DexEncodedMethod currentMethod, boolean fromMethodHandle) {
+      DexField field, ProgramMethod currentMethod, boolean fromMethodHandle) {
     if (!registerFieldRead(field, currentMethod)) {
       return false;
     }
@@ -1308,16 +1285,16 @@
     return true;
   }
 
-  boolean traceStaticFieldWrite(DexField field, DexEncodedMethod currentMethod) {
+  boolean traceStaticFieldWrite(DexField field, ProgramMethod currentMethod) {
     return traceStaticFieldWrite(field, currentMethod, false);
   }
 
-  boolean traceStaticFieldWriteFromMethodHandle(DexField field, DexEncodedMethod currentMethod) {
+  boolean traceStaticFieldWriteFromMethodHandle(DexField field, ProgramMethod currentMethod) {
     return traceStaticFieldWrite(field, currentMethod, true);
   }
 
   private boolean traceStaticFieldWrite(
-      DexField field, DexEncodedMethod currentMethod, boolean fromMethodHandle) {
+      DexField field, ProgramMethod currentMethod, boolean fromMethodHandle) {
     if (!registerFieldWrite(field, currentMethod)) {
       return false;
     }
@@ -1368,17 +1345,17 @@
   }
 
   private Function<DexProgramClass, KeepReasonWitness> classReferencedFromReporter(
-      DexEncodedMethod currentMethod) {
+      ProgramMethod currentMethod) {
     return clazz -> graphReporter.reportClassReferencedFrom(clazz, currentMethod);
   }
 
-  private DexMethod getInvokeSuperTarget(DexMethod method, DexEncodedMethod currentMethod) {
+  private DexMethod getInvokeSuperTarget(DexMethod method, ProgramMethod currentMethod) {
     DexClass methodHolderClass = appView.definitionFor(method.holder);
     if (methodHolderClass != null && methodHolderClass.isInterface()) {
       return method;
     }
-    DexClass holderClass = appView.definitionFor(currentMethod.holder());
-    if (holderClass == null || holderClass.superType == null || holderClass.isInterface()) {
+    DexProgramClass holderClass = currentMethod.getHolder();
+    if (holderClass.superType == null || holderClass.isInterface()) {
       // We do not know better or this call is made from an interface.
       return method;
     }
@@ -1390,9 +1367,10 @@
   // Actual actions performed.
   //
 
-  private boolean verifyMethodIsTargeted(DexEncodedMethod method) {
-    assert !method.isClassInitializer() : "Class initializers are never targeted";
-    assert targetedMethods.contains(method);
+  private boolean verifyMethodIsTargeted(ProgramMethod method) {
+    DexEncodedMethod definition = method.getDefinition();
+    assert !definition.isClassInitializer() : "Class initializers are never targeted";
+    assert targetedMethods.contains(definition);
     return true;
   }
 
@@ -1539,13 +1517,12 @@
 
   private void ensureMethodsContinueToWidenAccess(
       DexProgramClass clazz, ScopedDexMethodSet seen, KeepReason reason) {
-    for (DexEncodedMethod method : clazz.virtualMethods()) {
-      if (seen.addMethodIfMoreVisible(method) == AddMethodIfMoreVisibleResult.ADDED_MORE_VISIBLE
-          && clazz.isProgramClass()
-          && appView.appInfo().methodDefinedInInterfaces(method, clazz.type)) {
-        markMethodAsTargeted(clazz, method, reason);
-      }
-    }
+    clazz.forEachProgramVirtualMethodMatching(
+        definition ->
+            seen.addMethodIfMoreVisible(definition)
+                    == AddMethodIfMoreVisibleResult.ADDED_MORE_VISIBLE
+                && appView.appInfo().methodDefinedInInterfaces(definition, clazz.type),
+        method -> markMethodAsTargeted(method, reason));
   }
 
   private void markInterfaceTypeAsLiveViaInheritanceClause(
@@ -1595,9 +1572,7 @@
   }
 
   private void enqueueHolderWithDependentInstanceConstructor(
-      DexProgramClass clazz,
-      DexEncodedMethod instanceInitializer,
-      Set<ProguardKeepRuleBase> reasons) {
+      DexProgramClass clazz, ProgramMethod instanceInitializer, Set<ProguardKeepRuleBase> reasons) {
     enqueueRootItem(clazz, reasons);
   }
 
@@ -1677,8 +1652,8 @@
     return resolutionResult.asSingleResolution();
   }
 
-  private void handleInvokeOfStaticTarget(DexMethod method, KeepReason reason) {
-    SingleResolutionResult resolution = resolveMethod(method, reason);
+  private void handleInvokeOfStaticTarget(DexMethod reference, KeepReason reason) {
+    SingleResolutionResult resolution = resolveMethod(reference, reason);
     if (resolution == null || resolution.getResolvedHolder().isNotProgramClass()) {
       return;
     }
@@ -1687,12 +1662,13 @@
 
     // We have to mark the resolved method as targeted even if it cannot actually be invoked
     // to make sure the invocation will keep failing in the appropriate way.
-    markMethodAsTargeted(clazz, encodedMethod, reason);
+    ProgramMethod method = new ProgramMethod(clazz, encodedMethod);
+    markMethodAsTargeted(method, reason);
 
     // Only mark methods for which invocation will succeed at runtime live.
     if (encodedMethod.isStatic()) {
       markDirectAndIndirectClassInitializersAsLive(clazz);
-      markDirectStaticOrConstructorMethodAsLive(clazz, encodedMethod, reason);
+      markDirectStaticOrConstructorMethodAsLive(method, reason);
     }
   }
 
@@ -1719,13 +1695,13 @@
 
   /** Returns true if the class initializer became live for the first time. */
   private boolean markDirectClassInitializerAsLive(DexProgramClass clazz) {
-    DexEncodedMethod clinit = clazz.getClassInitializer();
+    ProgramMethod clinit = clazz.getProgramClassInitializer();
     KeepReasonWitness witness = graphReporter.reportReachableClassInitializer(clazz, clinit);
     if (!initializedTypes.add(clazz, witness)) {
       return false;
     }
-    if (clinit != null && clinit.getOptimizationInfo().mayHaveSideEffects()) {
-      markDirectStaticOrConstructorMethodAsLive(clazz, clinit, witness);
+    if (clinit != null && clinit.getDefinition().getOptimizationInfo().mayHaveSideEffects()) {
+      markDirectStaticOrConstructorMethodAsLive(clinit, witness);
     }
     return true;
   }
@@ -1735,37 +1711,39 @@
     handleInvokeOfDirectTarget(method, reason);
   }
 
-  private void handleInvokeOfDirectTarget(DexMethod method, KeepReason reason) {
-    DexType holder = method.holder;
+  private void handleInvokeOfDirectTarget(DexMethod reference, KeepReason reason) {
+    DexType holder = reference.holder;
     DexProgramClass clazz = getProgramClassOrNull(holder);
     if (clazz == null) {
-      recordMethodReference(method);
+      recordMethodReference(reference);
       return;
     }
     // TODO(zerny): Is it ok that we lookup in both the direct and virtual pool here?
-    DexEncodedMethod encodedMethod = clazz.lookupMethod(method);
+    DexEncodedMethod encodedMethod = clazz.lookupMethod(reference);
     if (encodedMethod == null) {
-      reportMissingMethod(method);
+      reportMissingMethod(reference);
       return;
     }
 
+    ProgramMethod method = new ProgramMethod(clazz, encodedMethod);
+
     // We have to mark the resolved method as targeted even if it cannot actually be invoked
     // to make sure the invocation will keep failing in the appropriate way.
-    markMethodAsTargeted(clazz, encodedMethod, reason);
+    markMethodAsTargeted(method, reason);
 
     // Only mark methods for which invocation will succeed at runtime live.
     if (encodedMethod.isStatic()) {
       return;
     }
 
-    markDirectStaticOrConstructorMethodAsLive(clazz, encodedMethod, reason);
+    markDirectStaticOrConstructorMethodAsLive(method, reason);
 
     // It is valid to have an invoke-direct instruction in a default interface method that
     // 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)) {
-      enqueueMarkMethodLiveAction(clazz, encodedMethod, reason);
+      enqueueMarkMethodLiveAction(method, reason);
     }
   }
 
@@ -1839,26 +1817,26 @@
     }
   }
 
-  private void markMethodAsTargeted(
-      DexProgramClass clazz, DexEncodedMethod method, KeepReason reason) {
-    assert method.holder() == clazz.type;
-    if (!targetedMethods.add(method, reason)) {
+  private void markMethodAsTargeted(ProgramMethod method, KeepReason reason) {
+    DexEncodedMethod definition = method.getDefinition();
+    DexProgramClass holder = method.getHolder();
+    if (!targetedMethods.add(definition, reason)) {
       // Already targeted.
       return;
     }
     markReferencedTypesAsLive(method);
-    processAnnotations(clazz, method);
-    method.parameterAnnotationsList.forEachAnnotation(
-        annotation -> processAnnotation(clazz, method, annotation));
+    processAnnotations(holder, definition);
+    definition.parameterAnnotationsList.forEachAnnotation(
+        annotation -> processAnnotation(holder, definition, annotation));
 
     if (Log.ENABLED) {
-      Log.verbose(getClass(), "Method `%s` is targeted.", method.method);
+      Log.verbose(getClass(), "Method `%s` is targeted.", method);
     }
     if (forceProguardCompatibility) {
       // Keep targeted default methods in compatibility mode. The tree pruner will otherwise make
       // these methods abstract, whereas Proguard does not (seem to) touch their code.
-      if (!method.accessFlags.isAbstract() && clazz.isInterface()) {
-        markMethodAsLiveWithCompatRule(clazz, method);
+      if (!definition.isAbstract() && holder.isInterface()) {
+        markMethodAsLiveWithCompatRule(method);
       }
     }
   }
@@ -1870,7 +1848,7 @@
   // Package protected due to entry point from worklist.
   void processNewlyInstantiatedClass(
       DexProgramClass clazz,
-      DexEncodedMethod context,
+      ProgramMethod context,
       InstantiationReason instantiationReason,
       KeepReason keepReason) {
     assert !clazz.isAnnotation();
@@ -1905,7 +1883,7 @@
   // TODO(b/146016987): Make this the single instantiation entry rather than the worklist action.
   private boolean markInstantiatedClass(
       DexProgramClass clazz,
-      DexEncodedMethod context,
+      ProgramMethod context,
       InstantiationReason instantiationReason,
       KeepReason keepReason) {
     assert !clazz.isInterface();
@@ -1923,7 +1901,7 @@
     transitionDependentItemsForInstantiatedInterface(clazz);
   }
 
-  private void markLambdaAsInstantiated(LambdaDescriptor descriptor, DexEncodedMethod context) {
+  private void markLambdaAsInstantiated(LambdaDescriptor descriptor, ProgramMethod context) {
     // Each descriptor is unique, so there is no check for already marking the lambda.
     for (DexType iface : descriptor.interfaces) {
       checkLambdaInterface(iface, context);
@@ -1931,13 +1909,13 @@
     }
   }
 
-  private void checkLambdaInterface(DexType itf, DexEncodedMethod context) {
+  private void checkLambdaInterface(DexType itf, ProgramMethod context) {
     DexClass clazz = definitionFor(itf);
     if (clazz == null) {
       StringDiagnostic message =
           new StringDiagnostic(
               "Lambda expression implements missing interface `" + itf.toSourceString() + "`",
-              appInfo.originFor(context.holder()));
+              context.getOrigin());
       options.reporter.warning(message);
     } else if (!clazz.isInterface()) {
       StringDiagnostic message =
@@ -1946,7 +1924,7 @@
                   + "`"
                   + itf.toSourceString()
                   + "`",
-              appInfo.originFor(context.holder()));
+              context.getOrigin());
       options.reporter.warning(message);
     }
   }
@@ -2040,8 +2018,7 @@
     // simply marked live on its holder.
     if (resolutionMethod.getDefinition().isPrivateMethod()) {
       markVirtualMethodAsLive(
-          resolutionMethod.getHolder(),
-          resolutionMethod.getDefinition(),
+          resolutionMethod,
           graphReporter.reportReachableMethodAsLive(
               resolutionMethod.getDefinition().method, resolutionMethod));
       return;
@@ -2194,7 +2171,7 @@
     }
   }
 
-  private void markFieldAsTargeted(DexField field, DexEncodedMethod context) {
+  private void markFieldAsTargeted(DexField field, ProgramMethod context) {
     markTypeAsLive(field.type, clazz -> graphReporter.reportClassReferencedFrom(clazz, context));
     markTypeAsLive(field.holder, clazz -> graphReporter.reportClassReferencedFrom(clazz, context));
   }
@@ -2256,34 +2233,28 @@
     analyses.forEach(analysis -> analysis.processNewlyLiveField(field));
   }
 
-  private void markDirectStaticOrConstructorMethodAsLive(
-      DexProgramClass clazz, DexEncodedMethod encodedMethod, KeepReason reason) {
-    assert encodedMethod.holder() == clazz.type;
-
-    if (!enqueueMarkMethodLiveAction(clazz, encodedMethod, reason)) {
+  private void markDirectStaticOrConstructorMethodAsLive(ProgramMethod method, KeepReason reason) {
+    if (!enqueueMarkMethodLiveAction(method, reason)) {
       // Already marked live.
       return;
     }
     // Should already have marked the type live previously.
-    DexMethod method = encodedMethod.method;
-    assert encodedMethod.isClassInitializer() || verifyMethodIsTargeted(encodedMethod);
-    assert verifyTypeIsLive(clazz);
+    assert method.getDefinition().isClassInitializer() || verifyMethodIsTargeted(method);
+    assert verifyTypeIsLive(method.getHolder());
     if (Log.ENABLED) {
-      Log.verbose(
-          getClass(), "Method `%s` has become live due to direct invoke", encodedMethod.method);
+      Log.verbose(getClass(), "Method `%s` has become live due to direct invoke", method);
     }
   }
 
-  private void markVirtualMethodAsLive(
-      DexProgramClass clazz, DexEncodedMethod method, KeepReason reason) {
+  private void markVirtualMethodAsLive(ProgramMethod method, KeepReason reason) {
     assert method != null;
     // Only explicit keep rules or reflective use should make abstract methods live.
-    assert !method.accessFlags.isAbstract()
+    assert !method.getDefinition().isAbstract()
         || reason.isDueToKeepRule()
         || reason.isDueToReflectiveUse();
-    if (enqueueMarkMethodLiveAction(clazz, method, reason)) {
+    if (enqueueMarkMethodLiveAction(method, reason)) {
       if (Log.ENABLED) {
-        Log.verbose(getClass(), "Adding virtual method `%s` to live set.", method.method);
+        Log.verbose(getClass(), "Adding virtual method `%s` to live set.", method);
       }
     }
   }
@@ -2434,7 +2405,7 @@
     // need at least an abstract version of it so that it can be targeted.
     DexProgramClass resolvedHolder = resolution.getResolvedHolder().asProgramClass();
     DexEncodedMethod resolvedMethod = resolution.getResolvedMethod();
-    markMethodAsTargeted(resolvedHolder, resolvedMethod, reason);
+    markMethodAsTargeted(new ProgramMethod(resolvedHolder, resolvedMethod), reason);
 
     DexProgramClass context = contextOrNull == null ? null : contextOrNull.getHolder();
     if (contextOrNull != null
@@ -2484,8 +2455,7 @@
       DexClassAndMethod target, Function<ProgramMethod, KeepReasonWitness> reason) {
     ProgramMethod programMethod = target.asProgramMethod();
     if (programMethod != null && !programMethod.getDefinition().isAbstract()) {
-      markVirtualMethodAsLive(
-          programMethod.getHolder(), programMethod.getDefinition(), reason.apply(programMethod));
+      markVirtualMethodAsLive(programMethod, reason.apply(programMethod));
     }
   }
 
@@ -2493,10 +2463,7 @@
       LookupLambdaTarget target, Function<ProgramMethod, KeepReasonWitness> reason) {
     ProgramMethod implementationMethod = target.getImplementationMethod().asProgramMethod();
     if (implementationMethod != null) {
-      enqueueMarkMethodLiveAction(
-          implementationMethod.getHolder(),
-          implementationMethod.getDefinition(),
-          reason.apply(implementationMethod));
+      enqueueMarkMethodLiveAction(implementationMethod, reason.apply(implementationMethod));
     }
   }
 
@@ -2508,7 +2475,7 @@
           DexProgramClass clazz = getProgramClassOrNull(method.holder());
           if (clazz != null) {
             failedResolutionTargets.add(method.method);
-            markMethodAsTargeted(clazz, method, reason);
+            markMethodAsTargeted(new ProgramMethod(clazz, method), reason);
           }
         });
   }
@@ -2530,28 +2497,28 @@
     if (valuesMethod != null) {
       // TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the
       // marking for not renaming it is in the root set.
-      workList.enqueueMarkMethodKeptAction(clazz, valuesMethod, reason);
+      workList.enqueueMarkMethodKeptAction(new ProgramMethod(clazz, valuesMethod), reason);
       pinnedItems.add(valuesMethod.toReference());
       rootSet.shouldNotBeMinified(valuesMethod.toReference());
     }
   }
 
   // Package protected due to entry point from worklist.
-  void markSuperMethodAsReachable(DexMethod method, DexEncodedMethod from) {
+  void markSuperMethodAsReachable(DexMethod reference, ProgramMethod from) {
     KeepReason reason = KeepReason.targetedBySuperFrom(from);
-    SingleResolutionResult resolution = resolveMethod(method, reason);
+    SingleResolutionResult resolution = resolveMethod(reference, reason);
     if (resolution == null) {
       return;
     }
     // If the resolution is in the program, mark it targeted.
     if (resolution.getResolvedHolder().isProgramClass()) {
       markMethodAsTargeted(
-          resolution.getResolvedHolder().asProgramClass(), resolution.getResolvedMethod(), reason);
+          new ProgramMethod(
+              resolution.getResolvedHolder().asProgramClass(), resolution.getResolvedMethod()),
+          reason);
     }
     // If invoke target is invalid (inaccessible or not an instance-method) record it and stop.
-    // TODO(b/146016987): We should be passing the full program context and not looking it up again.
-    DexProgramClass fromHolder = appInfo.definitionFor(from.holder()).asProgramClass();
-    DexEncodedMethod target = resolution.lookupInvokeSuperTarget(fromHolder, appInfo);
+    DexEncodedMethod target = resolution.lookupInvokeSuperTarget(from.getHolder(), appInfo);
     if (target == null) {
       failedResolutionTargets.add(resolution.getResolvedMethod().method);
       return;
@@ -2561,16 +2528,19 @@
     if (clazz == null) {
       return;
     }
+
+    ProgramMethod method = new ProgramMethod(clazz, target);
+
     if (Log.ENABLED) {
-      Log.verbose(getClass(), "Adding super constraint from `%s` to `%s`", from.method,
-          target.method);
+      Log.verbose(getClass(), "Adding super constraint from `%s` to `%s`", from, target.method);
     }
-    if (superInvokeDependencies.computeIfAbsent(
-        from, ignore -> Sets.newIdentityHashSet()).add(target)) {
+    if (superInvokeDependencies
+        .computeIfAbsent(from.getDefinition(), ignore -> ProgramMethodSet.create())
+        .add(method)) {
       if (liveMethods.contains(from)) {
-        markMethodAsTargeted(clazz, target, KeepReason.invokedViaSuperFrom(from));
+        markMethodAsTargeted(method, KeepReason.invokedViaSuperFrom(from));
         if (!target.accessFlags.isAbstract()) {
-          markVirtualMethodAsLive(clazz, target, KeepReason.invokedViaSuperFrom(from));
+          markVirtualMethodAsLive(method, KeepReason.invokedViaSuperFrom(from));
         }
       }
     }
@@ -2629,7 +2599,7 @@
 
   private static class SyntheticAdditions {
 
-    Map<DexType, Pair<DexProgramClass, DexEncodedMethod>> syntheticInstantiations =
+    Map<DexType, Pair<DexProgramClass, ProgramMethod>> syntheticInstantiations =
         new IdentityHashMap<>();
 
     Map<DexMethod, ProgramMethod> liveMethods = new IdentityHashMap<>();
@@ -2649,7 +2619,7 @@
     }
 
     void addInstantiatedClass(
-        DexProgramClass clazz, DexEncodedMethod context, boolean isMainDexClass) {
+        DexProgramClass clazz, ProgramMethod context, boolean isMainDexClass) {
       assert !syntheticInstantiations.containsKey(clazz.type);
       syntheticInstantiations.put(clazz.type, new Pair<>(clazz, context));
       if (isMainDexClass) {
@@ -2675,7 +2645,7 @@
 
     void amendApplication(Builder appBuilder) {
       assert !isEmpty();
-      for (Pair<DexProgramClass, DexEncodedMethod> clazzAndContext :
+      for (Pair<DexProgramClass, ProgramMethod> clazzAndContext :
           syntheticInstantiations.values()) {
         appBuilder.addProgramClass(clazzAndContext.getFirst());
       }
@@ -2690,7 +2660,7 @@
       KeepReasonWitness fakeReason = enqueuer.graphReporter.fakeReportShouldNotBeUsed();
 
       enqueuer.pinnedItems.addAll(pinnedMethods);
-      for (Pair<DexProgramClass, DexEncodedMethod> clazzAndContext :
+      for (Pair<DexProgramClass, ProgramMethod> clazzAndContext :
           syntheticInstantiations.values()) {
         enqueuer.workList.enqueueMarkInstantiatedAction(
             clazzAndContext.getFirst(),
@@ -2700,10 +2670,8 @@
       }
       for (ProgramMethod liveMethod : liveMethods.values()) {
         assert !enqueuer.targetedMethods.contains(liveMethod.getDefinition());
-        DexProgramClass holder = liveMethod.getHolder();
-        DexEncodedMethod method = liveMethod.getDefinition();
-        enqueuer.markMethodAsTargeted(holder, method, fakeReason);
-        enqueuer.enqueueMarkMethodLiveAction(holder, method, fakeReason);
+        enqueuer.markMethodAsTargeted(liveMethod, fakeReason);
+        enqueuer.enqueueMarkMethodLiveAction(liveMethod, fakeReason);
       }
     }
   }
@@ -2752,17 +2720,18 @@
       assert classesWithSerializableLambdas.isEmpty();
       return;
     }
-    for (Pair<LambdaClass, DexEncodedMethod> lambdaClassAndContext : lambdaClasses.values()) {
+    for (Pair<LambdaClass, ProgramMethod> lambdaClassAndContext : lambdaClasses.values()) {
       // Add all desugared classes to the application, main-dex list, and mark them instantiated.
       LambdaClass lambdaClass = lambdaClassAndContext.getFirst();
-      DexEncodedMethod context = lambdaClassAndContext.getSecond();
+      ProgramMethod context = lambdaClassAndContext.getSecond();
       DexProgramClass programClass = lambdaClass.getOrCreateLambdaClass();
       additions.addInstantiatedClass(programClass, context, lambdaClass.addToMainDexList.get());
       // Mark the instance constructor targeted and live.
       DexEncodedMethod constructor = programClass.lookupDirectMethod(lambdaClass.constructor);
       KeepReason reason = KeepReason.instantiatedIn(context);
-      markMethodAsTargeted(programClass, constructor, reason);
-      markDirectStaticOrConstructorMethodAsLive(programClass, constructor, reason);
+      ProgramMethod method = new ProgramMethod(programClass, constructor);
+      markMethodAsTargeted(method, reason);
+      markDirectStaticOrConstructorMethodAsLive(method, reason);
     }
 
     // Rewrite all of the invoke-dynamic instructions to lambda class instantiations.
@@ -2915,14 +2884,9 @@
               if (!liveMethods.contains(synthesizedClass.lookupMethod(method))) {
                 return;
               }
-              DexEncodedMethod accessor = lambda.target.ensureAccessibilityIfNeeded(false);
-              if (accessor == null) {
-                return;
-              }
-              DexProgramClass accessorClass = getProgramClassOrNull(accessor.holder());
-              assert accessorClass != null;
-              if (accessorClass != null) {
-                liveMethods.add(accessorClass, accessor, graphReporter.fakeReportShouldNotBeUsed());
+              ProgramMethod accessor = lambda.target.ensureAccessibilityIfNeeded(false);
+              if (accessor != null) {
+                liveMethods.add(accessor, graphReporter.fakeReportShouldNotBeUsed());
               }
             });
   }
@@ -2995,11 +2959,8 @@
     }
 
     // Generate first the callbacks since they may require extra wrappers.
-    List<DexEncodedMethod> callbacks = desugaredLibraryWrapperAnalysis.generateCallbackMethods();
-    for (DexEncodedMethod callback : callbacks) {
-      DexProgramClass clazz = getProgramClassOrNull(callback.holder());
-      additions.addLiveMethod(new ProgramMethod(clazz, callback));
-    }
+    ProgramMethodSet callbacks = desugaredLibraryWrapperAnalysis.generateCallbackMethods();
+    callbacks.forEach(additions::addLiveMethod);
 
     // Generate the wrappers.
     List<DexProgramClass> wrappers = desugaredLibraryWrapperAnalysis.generateWrappers();
@@ -3261,8 +3222,7 @@
             singleTargetHolder.isInterface(),
             null,
             graphReporter.fakeReportShouldNotBeUsed());
-        enqueueMarkMethodLiveAction(
-            singleTargetHolder, singleTargetMethod, graphReporter.fakeReportShouldNotBeUsed());
+        enqueueMarkMethodLiveAction(singleTarget, graphReporter.fakeReportShouldNotBeUsed());
       }
     }
     action.getAction().accept(builder);
@@ -3286,36 +3246,37 @@
   }
 
   // Package protected due to entry point from worklist.
-  void markMethodAsKept(DexProgramClass holder, DexEncodedMethod target, KeepReason reason) {
-    DexMethod method = target.method;
-    if (target.isVirtualMethod()) {
+  void markMethodAsKept(ProgramMethod target, KeepReason reason) {
+    DexEncodedMethod definition = target.getDefinition();
+    DexProgramClass holder = target.getHolder();
+    DexMethod reference = target.getReference();
+    if (definition.isVirtualMethod()) {
       // A virtual method. Mark it as reachable so that subclasses, if instantiated, keep
       // their overrides. However, we don't mark it live, as a keep rule might not imply that
       // the corresponding class is live.
-      markVirtualMethodAsReachable(method, holder.isInterface(), null, reason);
+      markVirtualMethodAsReachable(reference, holder.isInterface(), null, reason);
       if (holder.isInterface()) {
         // Reachability for default methods is based on live subtypes in general. For keep rules,
         // we need special handling as we essentially might have live subtypes that are outside of
         // the current compilation unit. Keep either the default-method or its implementation
         // method.
         // TODO(b/120959039): Codify the kept-graph expectations for these cases in tests.
-        if (target.isNonAbstractVirtualMethod()) {
-          markVirtualMethodAsLive(holder, target, reason);
+        if (definition.isNonAbstractVirtualMethod()) {
+          markVirtualMethodAsLive(target, reason);
         } else {
-          DexEncodedMethod implementation = target.getDefaultInterfaceMethodImplementation();
+          DexEncodedMethod implementation = definition.getDefaultInterfaceMethodImplementation();
           if (implementation != null) {
             DexProgramClass companion = getProgramClassOrNull(implementation.holder());
             markTypeAsLive(companion, graphReporter.reportCompanionClass(holder, companion));
             markVirtualMethodAsLive(
-                companion,
-                implementation,
-                graphReporter.reportCompanionMethod(target, implementation));
+                new ProgramMethod(companion, implementation),
+                graphReporter.reportCompanionMethod(definition, implementation));
           }
         }
       }
     } else {
-      markMethodAsTargeted(holder, target, reason);
-      markDirectStaticOrConstructorMethodAsLive(holder, target, reason);
+      markMethodAsTargeted(target, reason);
+      markDirectStaticOrConstructorMethodAsLive(target, reason);
     }
   }
 
@@ -3396,61 +3357,52 @@
   }
 
   // Package protected due to entry point from worklist.
-  void markMethodAsLive(DexEncodedMethod method, KeepReason reason) {
-    assert liveMethods.contains(method);
+  void markMethodAsLive(ProgramMethod method, KeepReason reason) {
+    DexProgramClass holder = method.getHolder();
+    DexEncodedMethod definition = method.getDefinition();
 
-    DexProgramClass clazz = getProgramClassOrNull(method.holder());
-    if (clazz == null) {
-      return;
+    assert liveMethods.contains(definition);
+
+    if (definition.isStatic()) {
+      markDirectAndIndirectClassInitializersAsLive(method.getHolder());
     }
 
-    if (method.isStatic()) {
-      markDirectAndIndirectClassInitializersAsLive(clazz);
-    }
-
-    Set<DexEncodedMethod> superCallTargets = superInvokeDependencies.get(method);
+    ProgramMethodSet superCallTargets = superInvokeDependencies.get(method.getDefinition());
     if (superCallTargets != null) {
-      for (DexEncodedMethod superCallTarget : superCallTargets) {
+      for (ProgramMethod superCallTarget : superCallTargets) {
         if (Log.ENABLED) {
-          Log.verbose(getClass(), "Found super invoke constraint on `%s`.", superCallTarget.method);
+          Log.verbose(getClass(), "Found super invoke constraint on `%s`.", superCallTarget);
         }
-        DexProgramClass targetClass = getProgramClassOrNull(superCallTarget.holder());
-        assert targetClass != null;
-        if (targetClass != null) {
-          markMethodAsTargeted(
-              targetClass, superCallTarget, KeepReason.invokedViaSuperFrom(method));
-          markVirtualMethodAsLive(
-              targetClass, superCallTarget, KeepReason.invokedViaSuperFrom(method));
-        }
+        markMethodAsTargeted(superCallTarget, KeepReason.invokedViaSuperFrom(method));
+        markVirtualMethodAsLive(superCallTarget, KeepReason.invokedViaSuperFrom(method));
       }
     }
     markParameterAndReturnTypesAsLive(method);
-    processAnnotations(clazz, method);
-    method.parameterAnnotationsList.forEachAnnotation(
-        annotation -> processAnnotation(clazz, method, annotation));
-    method.registerCodeReferences(useRegistryFactory.create(appView, clazz, method, this));
+    processAnnotations(holder, definition);
+    definition.parameterAnnotationsList.forEachAnnotation(
+        annotation -> processAnnotation(holder, definition, annotation));
+    method.registerCodeReferences(useRegistryFactory.create(appView, method, this));
 
     // Add all dependent members to the workqueue.
-    enqueueRootItems(rootSet.getDependentItems(method));
+    enqueueRootItems(rootSet.getDependentItems(definition));
 
     // Notify analyses.
     analyses.forEach(analysis -> analysis.processNewlyLiveMethod(method));
   }
 
-  private void markReferencedTypesAsLive(DexEncodedMethod method) {
+  private void markReferencedTypesAsLive(ProgramMethod method) {
     markTypeAsLive(
-        method.holder(), clazz -> graphReporter.reportClassReferencedFrom(clazz, method));
+        method.getHolderType(), clazz -> graphReporter.reportClassReferencedFrom(clazz, method));
     markParameterAndReturnTypesAsLive(method);
   }
 
-
-  private void markParameterAndReturnTypesAsLive(DexEncodedMethod method) {
-    for (DexType parameterType : method.method.proto.parameters.values) {
+  private void markParameterAndReturnTypesAsLive(ProgramMethod method) {
+    for (DexType parameterType : method.getDefinition().parameters().values) {
       markTypeAsLive(
           parameterType, clazz -> graphReporter.reportClassReferencedFrom(clazz, method));
     }
     markTypeAsLive(
-        method.method.proto.returnType,
+        method.getDefinition().returnType(),
         clazz -> graphReporter.reportClassReferencedFrom(clazz, method));
   }
 
@@ -3470,22 +3422,20 @@
     } else {
       workList.enqueueMarkInstantiatedAction(clazz, null, InstantiationReason.KEEP_RULE, witness);
       if (clazz.hasDefaultInitializer()) {
-        DexEncodedMethod defaultInitializer = clazz.getDefaultInitializer();
+        ProgramMethod defaultInitializer = clazz.getProgramDefaultInitializer();
         workList.enqueueMarkReachableDirectAction(
-            defaultInitializer.method,
-            graphReporter.reportCompatKeepDefaultInitializer(clazz, defaultInitializer));
+            defaultInitializer.getReference(),
+            graphReporter.reportCompatKeepDefaultInitializer(defaultInitializer));
       }
     }
   }
 
-  private void markMethodAsLiveWithCompatRule(DexProgramClass clazz, DexEncodedMethod method) {
-    enqueueMarkMethodLiveAction(clazz, method, graphReporter.reportCompatKeepMethod(clazz, method));
+  private void markMethodAsLiveWithCompatRule(ProgramMethod method) {
+    enqueueMarkMethodLiveAction(method, graphReporter.reportCompatKeepMethod(method));
   }
 
-  private void handleReflectiveBehavior(DexEncodedMethod method) {
-    DexType originHolder = method.holder();
-    Origin origin = appInfo.originFor(originHolder);
-    IRCode code = method.buildIR(appView, origin);
+  private void handleReflectiveBehavior(ProgramMethod method) {
+    IRCode code = method.buildIR(appView);
     InstructionIterator iterator = code.instructionIterator();
     while (iterator.hasNext()) {
       Instruction instruction = iterator.next();
@@ -3493,7 +3443,7 @@
     }
   }
 
-  private void handleReflectiveBehavior(DexEncodedMethod method, Instruction instruction) {
+  private void handleReflectiveBehavior(ProgramMethod method, Instruction instruction) {
     if (!instruction.isInvokeMethod()) {
       return;
     }
@@ -3542,10 +3492,10 @@
         workList.enqueueMarkInstantiatedAction(
             clazz, null, InstantiationReason.REFLECTION, KeepReason.reflectiveUseIn(method));
         if (clazz.hasDefaultInitializer()) {
-          DexEncodedMethod initializer = clazz.getDefaultInitializer();
+          ProgramMethod initializer = clazz.getProgramDefaultInitializer();
           KeepReason reason = KeepReason.reflectiveUseIn(method);
-          markMethodAsTargeted(clazz, initializer, reason);
-          markDirectStaticOrConstructorMethodAsLive(clazz, initializer, reason);
+          markMethodAsTargeted(initializer, reason);
+          markDirectStaticOrConstructorMethodAsLive(initializer, reason);
         }
       }
     } else if (identifierItem.isDexField()) {
@@ -3575,27 +3525,28 @@
       }
     } else {
       assert identifierItem.isDexMethod();
-      DexMethod targetedMethod = identifierItem.asDexMethod();
-      DexProgramClass clazz = getProgramClassOrNull(targetedMethod.holder);
+      DexMethod targetedMethodReference = identifierItem.asDexMethod();
+      DexProgramClass clazz = getProgramClassOrNull(targetedMethodReference.holder);
       if (clazz == null) {
         return;
       }
-      DexEncodedMethod encodedMethod = appView.definitionFor(targetedMethod);
-      if (encodedMethod == null) {
+      DexEncodedMethod targetedMethodDefinition = clazz.lookupMethod(targetedMethodReference);
+      if (targetedMethodDefinition == null) {
         return;
       }
+      ProgramMethod targetedMethod = new ProgramMethod(clazz, targetedMethodDefinition);
       KeepReason reason = KeepReason.reflectiveUseIn(method);
-      if (encodedMethod.accessFlags.isStatic() || encodedMethod.accessFlags.isConstructor()) {
-        markMethodAsTargeted(clazz, encodedMethod, reason);
-        markDirectStaticOrConstructorMethodAsLive(clazz, encodedMethod, reason);
+      if (targetedMethodDefinition.isStatic() || targetedMethodDefinition.isInstanceInitializer()) {
+        markMethodAsTargeted(targetedMethod, reason);
+        markDirectStaticOrConstructorMethodAsLive(targetedMethod, reason);
       } else {
-        markVirtualMethodAsLive(clazz, encodedMethod, reason);
+        markVirtualMethodAsLive(targetedMethod, reason);
       }
     }
   }
 
   /** Handles reflective uses of {@link Class#newInstance()}. */
-  private void handleJavaLangClassNewInstance(DexEncodedMethod method, InvokeMethod invoke) {
+  private void handleJavaLangClassNewInstance(ProgramMethod method, InvokeMethod invoke) {
     if (!invoke.isInvokeVirtual()) {
       assert false;
       return;
@@ -3614,18 +3565,18 @@
     if (clazz == null) {
       return;
     }
-    DexEncodedMethod defaultInitializer = clazz.getDefaultInitializer();
+    ProgramMethod defaultInitializer = clazz.getProgramDefaultInitializer();
     if (defaultInitializer != null) {
       KeepReason reason = KeepReason.reflectiveUseIn(method);
       markClassAsInstantiatedWithReason(clazz, reason);
-      markMethodAsTargeted(clazz, defaultInitializer, reason);
-      markDirectStaticOrConstructorMethodAsLive(clazz, defaultInitializer, reason);
+      markMethodAsTargeted(defaultInitializer, reason);
+      markDirectStaticOrConstructorMethodAsLive(defaultInitializer, reason);
     }
   }
 
   /** Handles reflective uses of {@link java.lang.reflect.Constructor#newInstance(Object...)}. */
   private void handleJavaLangReflectConstructorNewInstance(
-      DexEncodedMethod method, InvokeMethod invoke) {
+      ProgramMethod method, InvokeMethod invoke) {
     if (!invoke.isInvokeVirtual()) {
       assert false;
       return;
@@ -3669,11 +3620,11 @@
       return;
     }
 
-    DexEncodedMethod initializer = null;
+    ProgramMethod initializer = null;
 
     int parametersSize = parametersSizeValue.definition.asConstNumber().getIntValue();
     if (parametersSize == 0) {
-      initializer = clazz.getDefaultInitializer();
+      initializer = clazz.getProgramDefaultInitializer();
     } else {
       DexType[] parameterTypes = new DexType[parametersSize];
       int missingIndices = parametersSize;
@@ -3711,15 +3662,15 @@
       }
 
       if (missingIndices == 0) {
-        initializer = clazz.getInitializer(parameterTypes);
+        initializer = clazz.getProgramInitializer(parameterTypes);
       }
     }
 
     if (initializer != null) {
       KeepReason reason = KeepReason.reflectiveUseIn(method);
       markClassAsInstantiatedWithReason(clazz, reason);
-      markMethodAsTargeted(clazz, initializer, reason);
-      markDirectStaticOrConstructorMethodAsLive(clazz, initializer, reason);
+      markMethodAsTargeted(initializer, reason);
+      markDirectStaticOrConstructorMethodAsLive(initializer, reason);
     }
   }
 
@@ -3728,7 +3679,7 @@
    * Class[], InvocationHandler)}.
    */
   private void handleJavaLangReflectProxyNewProxyInstance(
-      DexEncodedMethod method, InvokeMethod invoke) {
+      ProgramMethod method, InvokeMethod invoke) {
     if (!invoke.isInvokeStatic()) {
       assert false;
       return;
@@ -3770,7 +3721,7 @@
     }
   }
 
-  private void handleJavaLangEnumValueOf(DexEncodedMethod method, InvokeMethod invoke) {
+  private void handleJavaLangEnumValueOf(ProgramMethod method, InvokeMethod invoke) {
     // The use of java.lang.Enum.valueOf(java.lang.Class, java.lang.String) will indirectly
     // access the values() method of the enum class passed as the first argument. The method
     // SomeEnumClass.valueOf(java.lang.String) which is generated by javac for all enums will
@@ -3779,13 +3730,12 @@
       DexType type = invoke.inValues().get(0).definition.asConstClass().getValue();
       DexProgramClass clazz = getProgramClassOrNull(type);
       if (clazz != null && clazz.accessFlags.isEnum()) {
-        DexProgramClass holder = getProgramClassOrNull(method.holder());
-        markEnumValuesAsReachable(clazz, KeepReason.invokedFrom(holder, method));
+        markEnumValuesAsReachable(clazz, KeepReason.invokedFrom(method));
       }
     }
   }
 
-  private void handleServiceLoaderInvocation(DexEncodedMethod method, InvokeMethod invoke) {
+  private void handleServiceLoaderInvocation(ProgramMethod method, InvokeMethod invoke) {
     if (invoke.inValues().size() == 0) {
       // Should never happen.
       return;
@@ -3804,7 +3754,7 @@
                       + "` is being passed to the method `"
                       + invoke.getInvokedMethod().toSourceString()
                       + "`, but was not found in `META-INF/services/`.",
-                  appInfo.originFor(method.holder())));
+                  method.getOrigin()));
         }
         return;
       }
@@ -3877,16 +3827,21 @@
       this.register = register;
     }
 
-    boolean add(DexProgramClass clazz, DexEncodedMethod method, KeepReason reason) {
-      register.accept(method, reason);
-      transitionUnusedInterfaceToLive(clazz);
-      return items.add(method);
+    boolean add(ProgramMethod method, KeepReason reason) {
+      DexEncodedMethod definition = method.getDefinition();
+      register.accept(definition, reason);
+      transitionUnusedInterfaceToLive(method.getHolder());
+      return items.add(definition);
     }
 
     boolean contains(DexEncodedMethod method) {
       return items.contains(method);
     }
 
+    boolean contains(ProgramMethod method) {
+      return contains(method.getDefinition());
+    }
+
     Set<DexEncodedMethod> getItems() {
       return Collections.unmodifiableSet(items);
     }
@@ -3946,7 +3901,7 @@
       if (target != null) {
         // There is no dispatch on annotations, so only keep what is directly referenced.
         if (target.field == field) {
-          if (!registerFieldRead(field, DexEncodedMethod.ANNOTATION_REFERENCE)) {
+          if (!registerFieldReadFromAnnotation(field)) {
             return false;
           }
           markStaticFieldAsLive(target, KeepReason.referencedInAnnotation(annotationHolder));
@@ -3983,13 +3938,16 @@
         // There is no dispatch on annotations, so only keep what is directly referenced.
         if (target.method == method) {
           markDirectStaticOrConstructorMethodAsLive(
-              holder, target, KeepReason.referencedInAnnotation(annotationHolder));
+              new ProgramMethod(holder, target),
+              KeepReason.referencedInAnnotation(annotationHolder));
         }
       } else {
         target = holder.lookupVirtualMethod(method);
         // There is no dispatch on annotations, so only keep what is directly referenced.
         if (target != null && target.method == method) {
-          markMethodAsTargeted(holder, target, KeepReason.referencedInAnnotation(annotationHolder));
+          markMethodAsTargeted(
+              new ProgramMethod(holder, target),
+              KeepReason.referencedInAnnotation(annotationHolder));
         }
       }
       return false;
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistryFactory.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistryFactory.java
index d32a130..86acd84 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistryFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistryFactory.java
@@ -5,15 +5,10 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 
 public interface EnqueuerUseRegistryFactory {
 
-  UseRegistry create(
-      AppView<?> appView,
-      DexProgramClass currentHolder,
-      DexEncodedMethod currentMethod,
-      Enqueuer enqueuer);
+  UseRegistry create(AppView<?> appView, ProgramMethod currentMethod, Enqueuer enqueuer);
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index 2a46ba3..f5ffc75 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -39,9 +38,9 @@
 
   static class MarkReachableSuperAction extends EnqueuerAction {
     final DexMethod target;
-    final DexEncodedMethod context;
+    final ProgramMethod context;
 
-    public MarkReachableSuperAction(DexMethod target, DexEncodedMethod context) {
+    public MarkReachableSuperAction(DexMethod target, ProgramMethod context) {
       this.target = target;
       this.context = context;
     }
@@ -70,13 +69,13 @@
   static class MarkInstantiatedAction extends EnqueuerAction {
 
     final DexProgramClass target;
-    final DexEncodedMethod context;
+    final ProgramMethod context;
     final InstantiationReason instantiationReason;
     final KeepReason keepReason;
 
     public MarkInstantiatedAction(
         DexProgramClass target,
-        DexEncodedMethod context,
+        ProgramMethod context,
         InstantiationReason instantiationReason,
         KeepReason keepReason) {
       this.target = target;
@@ -122,35 +121,32 @@
   }
 
   static class MarkMethodLiveAction extends EnqueuerAction {
-    final DexEncodedMethod target;
+    final ProgramMethod method;
     final KeepReason reason;
 
-    public MarkMethodLiveAction(DexEncodedMethod target, KeepReason reason) {
-      this.target = target;
+    public MarkMethodLiveAction(ProgramMethod method, KeepReason reason) {
+      this.method = method;
       this.reason = reason;
     }
 
     @Override
     public void run(Enqueuer enqueuer) {
-      enqueuer.markMethodAsLive(target, reason);
+      enqueuer.markMethodAsLive(method, reason);
     }
   }
 
   static class MarkMethodKeptAction extends EnqueuerAction {
-    final DexProgramClass holder;
-    final DexEncodedMethod target;
+    final ProgramMethod target;
     final KeepReason reason;
 
-    public MarkMethodKeptAction(
-        DexProgramClass holder, DexEncodedMethod target, KeepReason reason) {
-      this.holder = holder;
+    public MarkMethodKeptAction(ProgramMethod target, KeepReason reason) {
       this.target = target;
       this.reason = reason;
     }
 
     @Override
     public void run(Enqueuer enqueuer) {
-      enqueuer.markMethodAsKept(holder, target, reason);
+      enqueuer.markMethodAsKept(target, reason);
     }
   }
 
@@ -174,34 +170,31 @@
 
   static class TraceConstClassAction extends EnqueuerAction {
     final DexType type;
-    final DexEncodedMethod currentMethod;
+    final ProgramMethod context;
 
-    TraceConstClassAction(DexType type, DexEncodedMethod currentMethod) {
+    TraceConstClassAction(DexType type, ProgramMethod context) {
       this.type = type;
-      this.currentMethod = currentMethod;
+      this.context = context;
     }
 
     @Override
     public void run(Enqueuer enqueuer) {
-      enqueuer.traceConstClass(type, currentMethod);
+      enqueuer.traceConstClass(type, context);
     }
   }
 
   static class TraceInvokeDirectAction extends EnqueuerAction {
     final DexMethod invokedMethod;
-    final DexProgramClass currentHolder;
-    final DexEncodedMethod currentMethod;
+    final ProgramMethod context;
 
-    TraceInvokeDirectAction(
-        DexMethod invokedMethod, DexProgramClass currentHolder, DexEncodedMethod currentMethod) {
+    TraceInvokeDirectAction(DexMethod invokedMethod, ProgramMethod context) {
       this.invokedMethod = invokedMethod;
-      this.currentHolder = currentHolder;
-      this.currentMethod = currentMethod;
+      this.context = context;
     }
 
     @Override
     public void run(Enqueuer enqueuer) {
-      enqueuer.traceInvokeDirect(invokedMethod, new ProgramMethod(currentHolder, currentMethod));
+      enqueuer.traceInvokeDirect(invokedMethod, context);
     }
   }
 
@@ -222,16 +215,16 @@
 
   static class TraceStaticFieldReadAction extends EnqueuerAction {
     final DexField field;
-    final DexEncodedMethod currentMethod;
+    final ProgramMethod context;
 
-    TraceStaticFieldReadAction(DexField field, DexEncodedMethod currentMethod) {
+    TraceStaticFieldReadAction(DexField field, ProgramMethod context) {
       this.field = field;
-      this.currentMethod = currentMethod;
+      this.context = context;
     }
 
     @Override
     public void run(Enqueuer enqueuer) {
-      enqueuer.traceStaticFieldRead(field, currentMethod);
+      enqueuer.traceStaticFieldRead(field, context);
     }
   }
 
@@ -258,7 +251,7 @@
     queue.add(new MarkReachableDirectAction(method, reason));
   }
 
-  void enqueueMarkReachableSuperAction(DexMethod method, DexEncodedMethod from) {
+  void enqueueMarkReachableSuperAction(DexMethod method, ProgramMethod from) {
     queue.add(new MarkReachableSuperAction(method, from));
   }
 
@@ -272,7 +265,7 @@
   // Consider updating call sites with the context information to increase precision where possible.
   public void enqueueMarkInstantiatedAction(
       DexProgramClass clazz,
-      DexEncodedMethod context,
+      ProgramMethod context,
       InstantiationReason instantiationReason,
       KeepReason keepReason) {
     assert !clazz.isAnnotation();
@@ -292,16 +285,12 @@
     queue.add(new MarkInterfaceInstantiatedAction(clazz, reason));
   }
 
-  void enqueueMarkMethodLiveAction(
-      DexProgramClass clazz, DexEncodedMethod method, KeepReason reason) {
-    assert method.holder() == clazz.type;
+  void enqueueMarkMethodLiveAction(ProgramMethod method, KeepReason reason) {
     queue.add(new MarkMethodLiveAction(method, reason));
   }
 
-  void enqueueMarkMethodKeptAction(
-      DexProgramClass clazz, DexEncodedMethod method, KeepReason reason) {
-    assert method.holder() == clazz.type;
-    queue.add(new MarkMethodKeptAction(clazz, method, reason));
+  void enqueueMarkMethodKeptAction(ProgramMethod method, KeepReason reason) {
+    queue.add(new MarkMethodKeptAction(method, reason));
   }
 
   void enqueueMarkFieldKeptAction(
@@ -310,23 +299,19 @@
     queue.add(new MarkFieldKeptAction(holder, field, witness));
   }
 
-  public void enqueueTraceConstClassAction(DexType type, DexEncodedMethod currentMethod) {
-    assert currentMethod.isProgramMethod(appView);
-    queue.add(new TraceConstClassAction(type, currentMethod));
+  public void enqueueTraceConstClassAction(DexType type, ProgramMethod context) {
+    queue.add(new TraceConstClassAction(type, context));
   }
 
-  public void enqueueTraceInvokeDirectAction(
-      DexMethod invokedMethod, DexProgramClass currentHolder, DexEncodedMethod currentMethod) {
-    assert currentMethod.holder() == currentHolder.type;
-    queue.add(new TraceInvokeDirectAction(invokedMethod, currentHolder, currentMethod));
+  public void enqueueTraceInvokeDirectAction(DexMethod invokedMethod, ProgramMethod context) {
+    queue.add(new TraceInvokeDirectAction(invokedMethod, context));
   }
 
   public void enqueueTraceNewInstanceAction(DexType type, ProgramMethod context) {
     queue.add(new TraceNewInstanceAction(type, context));
   }
 
-  public void enqueueTraceStaticFieldRead(DexField field, DexEncodedMethod currentMethod) {
-    assert currentMethod.isProgramMethod(appView);
-    queue.add(new TraceStaticFieldReadAction(field, currentMethod));
+  public void enqueueTraceStaticFieldRead(DexField field, ProgramMethod context) {
+    queue.add(new TraceStaticFieldReadAction(field, context));
   }
 }
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 4ff3594..0e39b4c 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -169,21 +169,19 @@
     return KeepReasonWitness.INSTANCE;
   }
 
-  public KeepReasonWitness reportCompatKeepDefaultInitializer(
-      DexProgramClass holder, DexEncodedMethod defaultInitializer) {
-    assert holder.type == defaultInitializer.holder();
-    assert holder.getDefaultInitializer() == defaultInitializer;
+  public KeepReasonWitness reportCompatKeepDefaultInitializer(ProgramMethod defaultInitializer) {
+    assert defaultInitializer.getHolder().getDefaultInitializer()
+        == defaultInitializer.getDefinition();
     if (keptGraphConsumer != null) {
       reportEdge(
-          getClassGraphNode(holder.type),
-          getMethodGraphNode(defaultInitializer.method),
+          getClassGraphNode(defaultInitializer.getHolderType()),
+          getMethodGraphNode(defaultInitializer.getReference()),
           EdgeKind.CompatibilityRule);
     }
     return KeepReasonWitness.INSTANCE;
   }
 
-  public KeepReasonWitness reportCompatKeepMethod(DexProgramClass holder, DexEncodedMethod method) {
-    assert holder.type == method.holder();
+  public KeepReasonWitness reportCompatKeepMethod(ProgramMethod method) {
     // TODO(b/141729349): This compat rule is from the method to itself and has not edge. Fix it.
     // The rule is stating that if the method is targeted it is live. Since such an edge does
     // not contribute to additional information in the kept graph as it stands (no distinction
@@ -192,10 +190,10 @@
   }
 
   public KeepReasonWitness reportCompatInstantiated(
-      DexProgramClass instantiated, DexEncodedMethod method) {
+      DexProgramClass instantiated, ProgramMethod method) {
     if (keptGraphConsumer != null) {
       reportEdge(
-          getMethodGraphNode(method.method),
+          getMethodGraphNode(method.getReference()),
           getClassGraphNode(instantiated.type),
           EdgeKind.CompatibilityRule);
     }
@@ -212,10 +210,9 @@
     return KeepReasonWitness.INSTANCE;
   }
 
-  public KeepReasonWitness reportClassReferencedFrom(
-      DexProgramClass clazz, DexEncodedMethod method) {
+  public KeepReasonWitness reportClassReferencedFrom(DexProgramClass clazz, ProgramMethod method) {
     if (keptGraphConsumer != null) {
-      MethodGraphNode source = getMethodGraphNode(method.method);
+      MethodGraphNode source = getMethodGraphNode(method.getReference());
       ClassGraphNode target = getClassGraphNode(clazz.type);
       return reportEdge(source, target, EdgeKind.ReferencedFrom);
     }
@@ -232,13 +229,12 @@
   }
 
   public KeepReasonWitness reportReachableClassInitializer(
-      DexProgramClass clazz, DexEncodedMethod initializer) {
+      DexProgramClass clazz, ProgramMethod initializer) {
     if (initializer != null) {
-      assert clazz.type == initializer.holder();
-      assert initializer.isClassInitializer();
+      assert initializer.getDefinition().isClassInitializer();
       if (keptGraphConsumer != null) {
         ClassGraphNode source = getClassGraphNode(clazz.type);
-        MethodGraphNode target = getMethodGraphNode(initializer.method);
+        MethodGraphNode target = getMethodGraphNode(initializer.getReference());
         return reportEdge(source, target, EdgeKind.ReachableFromLiveType);
       }
     } else {
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 62a67ee..31f7611 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -29,10 +29,18 @@
     return new InstantiatedIn(method);
   }
 
+  static KeepReason instantiatedIn(ProgramMethod method) {
+    return new InstantiatedIn(method.getDefinition());
+  }
+
   public static KeepReason invokedViaSuperFrom(DexEncodedMethod from) {
     return new InvokedViaSuper(from);
   }
 
+  public static KeepReason invokedViaSuperFrom(ProgramMethod from) {
+    return new InvokedViaSuper(from.getDefinition());
+  }
+
   public static KeepReason reachableFromLiveType(DexType type) {
     return new ReachableFromLiveType(type);
   }
@@ -45,12 +53,12 @@
     return invokedFrom(context.getHolder(), context.getDefinition());
   }
 
-  public static KeepReason invokedFromLambdaCreatedIn(DexEncodedMethod method) {
-    return new InvokedFromLambdaCreatedIn(method);
+  public static KeepReason invokedFromLambdaCreatedIn(ProgramMethod method) {
+    return new InvokedFromLambdaCreatedIn(method.getDefinition());
   }
 
-  public static KeepReason fieldReferencedIn(DexEncodedMethod method) {
-    return new ReferencedFrom(method);
+  public static KeepReason fieldReferencedIn(ProgramMethod method) {
+    return new ReferencedFrom(method.getDefinition());
   }
 
   public static KeepReason referencedInAnnotation(DexItem holder) {
@@ -65,16 +73,16 @@
     return false;
   }
 
-  public static KeepReason targetedBySuperFrom(DexEncodedMethod from) {
-    return new TargetedBySuper(from);
+  public static KeepReason targetedBySuperFrom(ProgramMethod from) {
+    return new TargetedBySuper(from.getDefinition());
   }
 
-  public static KeepReason reflectiveUseIn(DexEncodedMethod method) {
-    return new ReflectiveUseFrom(method);
+  public static KeepReason reflectiveUseIn(ProgramMethod method) {
+    return new ReflectiveUseFrom(method.getDefinition());
   }
 
-  public static KeepReason methodHandleReferencedIn(DexEncodedMethod method) {
-    return new MethodHandleReferencedFrom(method);
+  public static KeepReason methodHandleReferencedIn(ProgramMethod method) {
+    return new MethodHandleReferencedFrom(method.getDefinition());
   }
 
   private abstract static class BasedOnOtherMethod extends KeepReason {
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 e686f98..205cf71 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -4,12 +4,13 @@
 
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 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;
@@ -18,7 +19,9 @@
 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.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.utils.BooleanBox;
 import java.util.Set;
 import java.util.function.Consumer;
 
@@ -39,30 +42,28 @@
 
   public void run(Set<DexType> roots) {
     for (DexType type : roots) {
-      DexClass clazz = appInfo.definitionFor(type);
+      DexProgramClass clazz = asProgramClassOrNull(appInfo.definitionFor(type));
       // Should only happen for library classes, which are filtered out.
       assert clazz != null;
       consumer.accept(type);
       // Super and interfaces are live, no need to add them.
       traceAnnotationsDirectDependencies(clazz.annotations());
       clazz.forEachField(field -> consumer.accept(field.field.type));
-      clazz.forEachMethod(method -> {
-        traceMethodDirectDependencies(method.method, consumer);
-        method.registerCodeReferences(codeDirectReferenceCollector);
-      });
+      clazz.forEachProgramMethodMatching(
+          definition -> {
+            traceMethodDirectDependencies(definition.getReference(), consumer);
+            return definition.hasCode();
+          },
+          method -> method.registerCodeReferences(codeDirectReferenceCollector));
     }
   }
 
-  public void runOnCode(DexEncodedMethod method) {
+  public void runOnCode(ProgramMethod method) {
     method.registerCodeReferences(codeDirectReferenceCollector);
   }
 
-  private static class BooleanBox {
-    boolean value = false;
-  }
-
   public static boolean hasReferencesOutsideFromCode(
-      AppInfoWithClassHierarchy appInfo, DexEncodedMethod method, Set<DexType> classes) {
+      AppInfoWithClassHierarchy appInfo, ProgramMethod method, Set<DexType> classes) {
 
     BooleanBox result = new BooleanBox();
 
@@ -73,13 +74,13 @@
               if (baseType.isClassType() && !classes.contains(baseType)) {
                 DexClass cls = appInfo.definitionFor(baseType);
                 if (cls != null && cls.isProgramClass()) {
-                  result.value = true;
+                  result.set(true);
                 }
               }
             })
         .runOnCode(method);
 
-    return result.value;
+    return result.get();
   }
 
   private void traceAnnotationsDirectDependencies(DexAnnotationSet annotations) {
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 846710b..e105350 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -1314,15 +1314,20 @@
     public void forEachDependentInstanceConstructor(
         DexProgramClass clazz,
         AppView<?> appView,
-        Consumer3<DexProgramClass, DexEncodedMethod, Set<ProguardKeepRuleBase>> fn) {
+        Consumer3<DexProgramClass, ProgramMethod, Set<ProguardKeepRuleBase>> fn) {
       getDependentItems(clazz)
           .forEach(
               (reference, reasons) -> {
-                DexDefinition definition = appView.definitionFor(reference);
-                if (definition != null
-                    && definition.isDexEncodedMethod()
-                    && definition.asDexEncodedMethod().isInstanceInitializer()) {
-                  fn.accept(clazz, definition.asDexEncodedMethod(), reasons);
+                if (reference.isDexMethod()) {
+                  DexMethod methodReference = reference.asDexMethod();
+                  DexProgramClass holder =
+                      asProgramClassOrNull(appView.definitionForHolder(methodReference));
+                  if (holder != null) {
+                    ProgramMethod method = holder.lookupProgramMethod(methodReference);
+                    if (method != null && method.getDefinition().isInstanceInitializer()) {
+                      fn.accept(clazz, method, reasons);
+                    }
+                  }
                 }
               });
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index 5031d82..dc69de3 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.utils.MethodJavaSignatureEquivalence;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.SingletonEquivalence;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.BiMap;
@@ -171,8 +172,8 @@
       }
       boolean classHasSynchronizedMethods = false;
       for (DexEncodedMethod method : clazz.methods()) {
-        assert !hasSynchronizedMethods || !method.accessFlags.isSynchronized();
-        classHasSynchronizedMethods |= method.accessFlags.isSynchronized();
+        assert !hasSynchronizedMethods || !method.isSynchronized();
+        classHasSynchronizedMethods |= method.isSynchronized();
         Wrapper<DexMethod> wrapper = methodEquivalence.wrap(method.method);
         methodBuckets.add(wrapper);
       }
@@ -443,14 +444,17 @@
 
     // Check that no methods access package-private or protected members.
     IllegalAccessDetector registry = new IllegalAccessDetector(appView, clazz);
-    for (DexEncodedMethod method : clazz.methods()) {
-      registry.setContext(method);
-      method.registerCodeReferences(registry);
-      if (registry.foundIllegalAccess()) {
-        return false;
-      }
-    }
-    return true;
+    TraversalContinuation result =
+        clazz.traverseProgramMethods(
+            method -> {
+              registry.setContext(method);
+              method.registerCodeReferences(registry);
+              if (registry.foundIllegalAccess()) {
+                return TraversalContinuation.BREAK;
+              }
+              return TraversalContinuation.CONTINUE;
+            });
+    return result.shouldContinue();
   }
 
   private void moveMembersFromSourceToTarget(
@@ -465,8 +469,8 @@
 
     // TODO(b/136457753) This check is a bit weird for protected, since it is moving access.
     assert targetClass.accessFlags.isAtLeastAsVisibleAs(sourceClass.accessFlags);
-    assert sourceClass.instanceFields().size() == 0;
-    assert targetClass.instanceFields().size() == 0;
+    assert sourceClass.instanceFields().isEmpty();
+    assert targetClass.instanceFields().isEmpty();
 
     numberOfMergedClasses++;
 
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 7c679e0..6f2010c 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -291,13 +291,13 @@
         // Also some other kinds of methods cannot be abstract, so keep them around.
         boolean allowAbstract =
             (!options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()
-                    || clazz.accessFlags.isAbstract())
-                && !method.accessFlags.isFinal()
+                    || clazz.isAbstract())
+                && !method.isFinal()
                 && !method.accessFlags.isNative()
                 && !method.accessFlags.isStrict()
-                && !method.accessFlags.isSynchronized()
+                && !method.isSynchronized()
                 && !method.accessFlags.isPrivate()
-                && !method.accessFlags.isStatic()
+                && !method.isStatic()
                 && !appInfo.failedResolutionTargets.contains(method.method);
         // 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
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 bb90d42..31d8074 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
@@ -32,6 +33,7 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.SubtypingInfo;
@@ -51,6 +53,7 @@
 import com.android.tools.r8.utils.FieldSignatureEquivalence;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.Iterables;
@@ -376,19 +379,21 @@
       //     * Have access to the no-arg constructor of its first non-serializable superclass
       return false;
     }
-    for (DexEncodedMethod method : sourceClass.directMethods()) {
-      // We rename constructors to private methods and mark them to be forced-inlined, so we have to
-      // check if we can force-inline all constructors.
-      if (method.isInstanceInitializer()) {
-        AbortReason reason = disallowInlining(method, targetClass.type);
-        if (reason != null) {
-          // Cannot guarantee that markForceInline() will work.
-          if (Log.ENABLED) {
-            reason.printLogMessageForClass(sourceClass);
-          }
-          return false;
-        }
-      }
+    TraversalContinuation result =
+        sourceClass.traverseProgramInstanceInitializers(
+            method -> {
+              AbortReason reason = disallowInlining(method, targetClass.type);
+              if (reason != null) {
+                // Cannot guarantee that markForceInline() will work.
+                if (Log.ENABLED) {
+                  reason.printLogMessageForClass(sourceClass);
+                }
+                return TraversalContinuation.BREAK;
+              }
+              return TraversalContinuation.CONTINUE;
+            });
+    if (result.shouldBreak()) {
+      return false;
     }
     if (sourceClass.getEnclosingMethod() != null || !sourceClass.getInnerClasses().isEmpty()) {
       // TODO(b/147504070): Consider merging of enclosing-method and inner-class attributes.
@@ -472,15 +477,13 @@
     return true;
   }
 
-  private boolean mergeMayLeadToIllegalAccesses(DexClass source, DexClass target) {
+  private boolean mergeMayLeadToIllegalAccesses(DexProgramClass source, DexProgramClass target) {
     if (source.type.isSamePackage(target.type)) {
       // When merging two classes from the same package, we only need to make sure that [source]
       // does not get less visible, since that could make a valid access to [source] from another
       // package illegal after [source] has been merged into [target].
-      int accessLevel =
-          source.accessFlags.isPrivate() ? 0 : (source.accessFlags.isPublic() ? 2 : 1);
-      int otherAccessLevel =
-          target.accessFlags.isPrivate() ? 0 : (target.accessFlags.isPublic() ? 2 : 1);
+      int accessLevel = source.isPrivate() ? 0 : (source.isPublic() ? 2 : 1);
+      int otherAccessLevel = target.isPrivate() ? 0 : (target.isPublic() ? 2 : 1);
       return accessLevel > otherAccessLevel;
     }
 
@@ -489,22 +492,22 @@
     // [source] are either private or public.
     //
     // (Deliberately not checking all accesses to [source] since that would be expensive.)
-    if (!target.accessFlags.isPublic()) {
+    if (!target.isPublic()) {
       return true;
     }
     for (DexEncodedField field : source.fields()) {
-      if (!(field.accessFlags.isPublic() || field.accessFlags.isPrivate())) {
+      if (!(field.isPublic() || field.isPrivate())) {
         return true;
       }
     }
     for (DexEncodedMethod method : source.methods()) {
-      if (!(method.accessFlags.isPublic() || method.accessFlags.isPrivate())) {
+      if (!(method.isPublic() || method.isPrivate())) {
         return true;
       }
       // Check if the target is overriding and narrowing the access.
-      if (method.accessFlags.isPublic()) {
+      if (method.isPublic()) {
         DexEncodedMethod targetOverride = target.lookupVirtualMethod(method.method);
-        if (targetOverride != null && !targetOverride.accessFlags.isPublic()) {
+        if (targetOverride != null && !targetOverride.isPublic()) {
           return true;
         }
       }
@@ -513,15 +516,17 @@
     // [source] will continue to work. This is guaranteed if the methods of [source] do not access
     // any private or protected classes or members from the current package of [source].
     IllegalAccessDetector registry = new IllegalAccessDetector(appView, source);
-    for (DexEncodedMethod method : source.methods()) {
-      registry.setContext(method);
-      method.registerCodeReferences(registry);
-      if (registry.foundIllegalAccess()) {
-        return true;
-      }
-    }
-
-    return false;
+    TraversalContinuation result =
+        source.traverseProgramMethods(
+            method -> {
+              registry.setContext(method);
+              method.registerCodeReferences(registry);
+              if (registry.foundIllegalAccess()) {
+                return TraversalContinuation.BREAK;
+              }
+              return TraversalContinuation.CONTINUE;
+            });
+    return result.shouldBreak();
   }
 
   private Collection<DexMethod> getInvokes() {
@@ -1650,15 +1655,16 @@
     }
   }
 
-  private AbortReason disallowInlining(DexEncodedMethod method, DexType invocationContext) {
+  private AbortReason disallowInlining(ProgramMethod method, DexType invocationContext) {
     if (appView.options().enableInlining) {
-      if (method.getCode().isCfCode()) {
-        CfCode code = method.getCode().asCfCode();
+      Code code = method.getDefinition().getCode();
+      if (code.isCfCode()) {
+        CfCode cfCode = code.asCfCode();
         ConstraintWithTarget constraint =
-            code.computeInliningConstraint(
+            cfCode.computeInliningConstraint(
                 method,
                 appView,
-                new SingleTypeMapperGraphLense(method.holder(), invocationContext),
+                new SingleTypeMapperGraphLense(method.getHolderType(), invocationContext),
                 invocationContext);
         if (constraint == ConstraintWithTarget.NEVER) {
           return AbortReason.UNSAFE_INLINING;
@@ -1761,8 +1767,8 @@
   // as [source].
   public static class IllegalAccessDetector extends UseRegistry {
 
-    private boolean foundIllegalAccess = false;
-    private DexMethod context = null;
+    private boolean foundIllegalAccess;
+    private ProgramMethod context;
 
     private final AppView<?> appView;
     private final DexClass source;
@@ -1777,8 +1783,8 @@
       return foundIllegalAccess;
     }
 
-    public void setContext(DexEncodedMethod context) {
-      this.context = context.method;
+    public void setContext(ProgramMethod context) {
+      this.context = context;
     }
 
     private boolean checkFieldReference(DexField field) {
@@ -1840,7 +1846,7 @@
     public boolean registerInvokeVirtual(DexMethod method) {
       assert context != null;
       GraphLenseLookupResult lookup =
-          appView.graphLense().lookupMethod(method, context, Type.VIRTUAL);
+          appView.graphLense().lookupMethod(method, context.getReference(), Type.VIRTUAL);
       return checkMethodReference(lookup.getMethod());
     }
 
@@ -1848,7 +1854,7 @@
     public boolean registerInvokeDirect(DexMethod method) {
       assert context != null;
       GraphLenseLookupResult lookup =
-          appView.graphLense().lookupMethod(method, context, Type.DIRECT);
+          appView.graphLense().lookupMethod(method, context.getReference(), Type.DIRECT);
       return checkMethodReference(lookup.getMethod());
     }
 
@@ -1856,7 +1862,7 @@
     public boolean registerInvokeStatic(DexMethod method) {
       assert context != null;
       GraphLenseLookupResult lookup =
-          appView.graphLense().lookupMethod(method, context, Type.STATIC);
+          appView.graphLense().lookupMethod(method, context.getReference(), Type.STATIC);
       return checkMethodReference(lookup.getMethod());
     }
 
@@ -1864,7 +1870,7 @@
     public boolean registerInvokeInterface(DexMethod method) {
       assert context != null;
       GraphLenseLookupResult lookup =
-          appView.graphLense().lookupMethod(method, context, Type.INTERFACE);
+          appView.graphLense().lookupMethod(method, context.getReference(), Type.INTERFACE);
       return checkMethodReference(lookup.getMethod());
     }
 
@@ -1872,7 +1878,7 @@
     public boolean registerInvokeSuper(DexMethod method) {
       assert context != null;
       GraphLenseLookupResult lookup =
-          appView.graphLense().lookupMethod(method, context, Type.SUPER);
+          appView.graphLense().lookupMethod(method, context.getReference(), Type.SUPER);
       return checkMethodReference(lookup.getMethod());
     }
 
diff --git a/src/main/java/com/android/tools/r8/utils/BooleanBox.java b/src/main/java/com/android/tools/r8/utils/BooleanBox.java
new file mode 100644
index 0000000..d31e352
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/BooleanBox.java
@@ -0,0 +1,24 @@
+// 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.utils;
+
+public class BooleanBox {
+
+  private boolean value;
+
+  public BooleanBox() {}
+
+  public BooleanBox(boolean initialValue) {
+    set(initialValue);
+  }
+
+  public boolean get() {
+    return value;
+  }
+
+  public void set(boolean value) {
+    this.value = value;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/IntBox.java b/src/main/java/com/android/tools/r8/utils/IntBox.java
new file mode 100644
index 0000000..9f4ac85
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/IntBox.java
@@ -0,0 +1,28 @@
+// 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.utils;
+
+public class IntBox {
+
+  private int value;
+
+  public IntBox() {}
+
+  public IntBox(int initialValue) {
+    set(initialValue);
+  }
+
+  public int get() {
+    return value;
+  }
+
+  public int getAndIncrement() {
+    return value++;
+  }
+
+  public void set(int value) {
+    this.value = value;
+  }
+}
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 7e34093..62aa29a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -34,6 +34,7 @@
 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.ProgramMethod;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
@@ -47,6 +48,7 @@
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.utils.IROrdering.IdentityIROrdering;
 import com.android.tools.r8.utils.IROrdering.NondeterministicIROrdering;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableList;
@@ -60,7 +62,6 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Deque;
@@ -254,7 +255,7 @@
   public boolean enableKotlinMetadataRewritingForRenamedClasses = true;
   public boolean encodeChecksums = false;
   public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
-  public boolean enableCfInterfaceMethodDesugaring = false;
+  public boolean cfToCfDesugar = false;
 
   public int callGraphLikelySpuriousCallEdgeThreshold = 50;
 
@@ -375,7 +376,8 @@
   }
 
   public boolean shouldKeepStackMapTable() {
-    return isDesugaredLibraryCompilation()
+    assert cfToCfDesugar || isRelocatorCompilation() || getProguardConfiguration() != null;
+    return cfToCfDesugar
         || isRelocatorCompilation()
         || getProguardConfiguration().getKeepAttributes().stackMapTable;
   }
@@ -645,8 +647,8 @@
   private final Map<Origin, List<InvalidParameterAnnotationInfo>> warningInvalidParameterAnnotations
       = new HashMap<>();
 
-  private final Map<Origin, List<Pair<DexEncodedMethod, String>>> warningInvalidDebugInfo
-      = new HashMap<>();
+  private final Map<Origin, List<Pair<ProgramMethod, String>>> warningInvalidDebugInfo =
+      new HashMap<>();
 
   // Don't read code from dex files. Used to extract non-code information from vdex files where
   // the code contains unsupported byte codes.
@@ -900,7 +902,7 @@
   }
 
   public void warningInvalidDebugInfo(
-      DexEncodedMethod method, Origin origin, InvalidDebugInfoException e) {
+      ProgramMethod method, Origin origin, InvalidDebugInfoException e) {
     if (invalidDebugInfoFatal) {
       throw new CompilationError("Fatal warning: Invalid debug info", e);
     }
@@ -962,7 +964,7 @@
     }
     if (warningInvalidDebugInfo.size() > 0) {
       int count = 0;
-      for (List<Pair<DexEncodedMethod, String>> methods : warningInvalidDebugInfo.values()) {
+      for (List<Pair<ProgramMethod, String>> methods : warningInvalidDebugInfo.values()) {
         count += methods.size();
       }
       reporter.info(
@@ -972,7 +974,7 @@
                   + (count == 1 ? " method." : " methods.")));
       for (Origin origin : new TreeSet<>(warningInvalidDebugInfo.keySet())) {
         StringBuilder builder = new StringBuilder("Methods with invalid locals information:");
-        for (Pair<DexEncodedMethod, String> method : warningInvalidDebugInfo.get(origin)) {
+        for (Pair<ProgramMethod, String> method : warningInvalidDebugInfo.get(origin)) {
           builder.append("\n  ").append(method.getFirst().toSourceString());
           builder.append("\n  ").append(method.getSecond());
         }
@@ -1089,7 +1091,7 @@
 
     public BiConsumer<AppInfoWithLiveness, Enqueuer.Mode> enqueuerInspector = null;
 
-    public Consumer<Deque<Collection<DexEncodedMethod>>> waveModifier = waves -> {};
+    public Consumer<Deque<ProgramMethodSet>> waveModifier = waves -> {};
 
     /**
      * If this flag is enabled, we will also compute the set of possible targets for invoke-
@@ -1183,7 +1185,7 @@
       public int numberOfProguardIfRuleMemberEvaluations = 0;
     }
 
-    public Consumer<DexEncodedMethod> callSiteOptimizationInfoInspector = null;
+    public Consumer<ProgramMethod> callSiteOptimizationInfoInspector = null;
   }
 
   @VisibleForTesting
@@ -1276,7 +1278,7 @@
     }
     return desugarState == DesugarState.ON
         && interfaceMethodDesugaring == OffOrAuto.Auto
-        && (!canUseDefaultAndStaticInterfaceMethods() || enableCfInterfaceMethodDesugaring);
+        && (!canUseDefaultAndStaticInterfaceMethods() || cfToCfDesugar);
   }
 
   public boolean isStringSwitchConversionEnabled() {
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index f570956..f941485 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -16,6 +16,15 @@
     return list.get(0);
   }
 
+  public static <T> int firstIndexMatching(List<T> list, Predicate<T> tester) {
+    for (int i = 0; i < list.size(); i++) {
+      if (tester.test(list.get(i))) {
+        return i;
+      }
+    }
+    return -1;
+  }
+
   public static <T> T last(List<T> list) {
     return list.get(list.size() - 1);
   }
@@ -37,6 +46,15 @@
     return result;
   }
 
+  public static <T> boolean removeFirstMatch(List<T> list, Predicate<T> element) {
+    int index = firstIndexMatching(list, element);
+    if (index >= 0) {
+      list.remove(index);
+      return true;
+    }
+    return false;
+  }
+
   public static <T extends Comparable<T>> boolean verifyListIsOrdered(List<T> list) {
     for (int i = list.size() - 1; i > 0; i--) {
       if (list.get(i).compareTo(list.get(i - 1)) < 0) {
diff --git a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
index e6076ca..1349732 100644
--- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -24,6 +24,13 @@
     return processItemsWithResults(items::forEach, consumer, executorService);
   }
 
+  public static <T, U, R, E extends Exception> Collection<R> processItemsWithResults(
+      Map<T, U> items, ThrowingBiFunction<T, U, R, E> consumer, ExecutorService executorService)
+      throws ExecutionException {
+    return processItemsWithResults(
+        items.entrySet(), arg -> consumer.apply(arg.getKey(), arg.getValue()), executorService);
+  }
+
   public static <T, R, E extends Exception> Collection<R> processItemsWithResults(
       ForEachable<T> items, ThrowingFunction<T, R, E> consumer, ExecutorService executorService)
       throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/utils/ThrowingBiFunction.java b/src/main/java/com/android/tools/r8/utils/ThrowingBiFunction.java
new file mode 100644
index 0000000..7cfccf0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ThrowingBiFunction.java
@@ -0,0 +1,16 @@
+// 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.utils;
+
+/**
+ * Similar to a {@link java.util.function.BiFunction} but throws a single {@link Throwable}.
+ *
+ * @param <S> the type of the first input
+ * @param <T> the type of the second input
+ * @param <E> the type of the {@link Throwable}
+ */
+@FunctionalInterface
+public interface ThrowingBiFunction<S, T, R, E extends Throwable> {
+  R apply(S s, T t) throws E;
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
new file mode 100644
index 0000000..b76c14c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/LongLivedProgramMethodSetBuilder.java
@@ -0,0 +1,42 @@
+// 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.utils.collections;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+public class LongLivedProgramMethodSetBuilder {
+
+  private Set<DexMethod> methods = Sets.newIdentityHashSet();
+
+  public LongLivedProgramMethodSetBuilder() {}
+
+  public void add(ProgramMethod method) {
+    methods.add(method.getReference());
+  }
+
+  public void addAll(Iterable<ProgramMethod> methods) {
+    methods.forEach(this::add);
+  }
+
+  public ProgramMethodSet build(AppView<AppInfoWithLiveness> appView) {
+    ProgramMethodSet result = ProgramMethodSet.create(methods.size());
+    for (DexMethod oldMethod : methods) {
+      DexMethod method = appView.graphLense().getRenamedMethodSignature(oldMethod);
+      DexProgramClass holder = appView.definitionForHolder(method).asProgramClass();
+      result.createAndAdd(holder, holder.lookupMethod(method));
+    }
+    return result;
+  }
+
+  public boolean isEmpty() {
+    return methods.isEmpty();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
new file mode 100644
index 0000000..1795899
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodSet.java
@@ -0,0 +1,119 @@
+// 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.utils.collections;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Stream;
+
+public class ProgramMethodSet implements Iterable<ProgramMethod> {
+
+  private static final ProgramMethodSet EMPTY = new ProgramMethodSet(ImmutableMap.of());
+
+  private Map<DexMethod, ProgramMethod> backing;
+
+  private ProgramMethodSet(Map<DexMethod, ProgramMethod> backing) {
+    this.backing = backing;
+  }
+
+  public static ProgramMethodSet create() {
+    return new ProgramMethodSet(new IdentityHashMap<>());
+  }
+
+  public static ProgramMethodSet create(int capacity) {
+    return new ProgramMethodSet(new IdentityHashMap<>(capacity));
+  }
+
+  public static ProgramMethodSet create(ProgramMethod element) {
+    ProgramMethodSet result = create();
+    result.add(element);
+    return result;
+  }
+
+  public static ProgramMethodSet createConcurrent() {
+    return new ProgramMethodSet(new ConcurrentHashMap<>());
+  }
+
+  public static ProgramMethodSet createLinked() {
+    return new ProgramMethodSet(new LinkedHashMap<>());
+  }
+
+  public static ProgramMethodSet empty() {
+    return EMPTY;
+  }
+
+  public boolean add(ProgramMethod method) {
+    ProgramMethod existing = backing.put(method.getReference(), method);
+    assert existing == null || existing.isStructurallyEqualTo(method);
+    return existing == null;
+  }
+
+  public void addAll(Iterable<ProgramMethod> methods) {
+    methods.forEach(this::add);
+  }
+
+  public void addAll(ProgramMethodSet methods) {
+    backing.putAll(methods.backing);
+  }
+
+  public boolean createAndAdd(DexProgramClass clazz, DexEncodedMethod definition) {
+    return add(new ProgramMethod(clazz, definition));
+  }
+
+  public boolean contains(DexEncodedMethod method) {
+    return backing.containsKey(method.getReference());
+  }
+
+  public boolean contains(ProgramMethod method) {
+    return backing.containsKey(method.getReference());
+  }
+
+  public void clear() {
+    backing.clear();
+  }
+
+  public boolean isEmpty() {
+    return backing.isEmpty();
+  }
+
+  @Override
+  public Iterator<ProgramMethod> iterator() {
+    return backing.values().iterator();
+  }
+
+  public boolean remove(DexMethod method) {
+    ProgramMethod existing = backing.remove(method);
+    return existing != null;
+  }
+
+  public boolean remove(DexEncodedMethod method) {
+    return remove(method.getReference());
+  }
+
+  public int size() {
+    return backing.size();
+  }
+
+  public Stream<ProgramMethod> stream() {
+    return backing.values().stream();
+  }
+
+  public Set<DexEncodedMethod> toDefinitionSet() {
+    assert backing instanceof IdentityHashMap;
+    Set<DexEncodedMethod> definitions = Sets.newIdentityHashSet();
+    forEach(method -> definitions.add(method.getDefinition()));
+    return definitions;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index d6732bd..edfb00c 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -19,12 +19,12 @@
     extends TestCompilerBuilder<
         D8Command, Builder, D8TestCompileResult, D8TestRunResult, D8TestBuilder> {
 
-  private D8TestBuilder(TestState state, Builder builder) {
-    super(state, builder, Backend.DEX);
+  private D8TestBuilder(TestState state, Builder builder, Backend backend) {
+    super(state, builder, backend);
   }
 
-  public static D8TestBuilder create(TestState state) {
-    return new D8TestBuilder(state, D8Command.builder(state.getDiagnosticsHandler()));
+  public static D8TestBuilder create(TestState state, Backend backend) {
+    return new D8TestBuilder(state, D8Command.builder(state.getDiagnosticsHandler()), backend);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 7c3ddff..7938666 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -134,8 +134,12 @@
     return ExternalR8TestBuilder.create(new TestState(temp), backend, runtime);
   }
 
+  public static D8TestBuilder testForD8(TemporaryFolder temp, Backend backend) {
+    return D8TestBuilder.create(new TestState(temp), backend);
+  }
+
   public static D8TestBuilder testForD8(TemporaryFolder temp) {
-    return D8TestBuilder.create(new TestState(temp));
+    return D8TestBuilder.create(new TestState(temp), Backend.DEX);
   }
 
   public static DXTestBuilder testForDX(TemporaryFolder temp) {
@@ -171,7 +175,11 @@
   }
 
   public D8TestBuilder testForD8() {
-    return testForD8(temp);
+    return testForD8(temp, Backend.DEX);
+  }
+
+  public D8TestBuilder testForD8(Backend backend) {
+    return testForD8(temp, backend);
   }
 
   public DXTestBuilder testForDX() {
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
index 44f046b..eba971d 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -50,7 +50,11 @@
 
   @Override
   public void warning(Diagnostic warning) {
-    warnings.add(warning);
+    // When testing D8 with class file output this warning is always emitted. Discard this, as
+    // for tests this is not relevant.
+    if (!warning.equals("Compiling to Java class files with D8 is not officially supported")) {
+      warnings.add(warning);
+    }
   }
 
   @Override
@@ -58,14 +62,17 @@
     errors.add(error);
   }
 
+  @Override
   public List<Diagnostic> getInfos() {
     return infos;
   }
 
+  @Override
   public List<Diagnostic> getWarnings() {
     return warnings;
   }
 
+  @Override
   public List<Diagnostic> getErrors() {
     return errors;
   }
@@ -80,6 +87,7 @@
         messages.size());
   }
 
+  @Override
   public TestDiagnosticMessages assertNoMessages() {
     assertEmpty("info", getInfos());
     assertEmpty("warning", getWarnings());
@@ -87,6 +95,7 @@
     return this;
   }
 
+  @Override
   public TestDiagnosticMessages assertOnlyInfos() {
     assertNotEquals(0, getInfos().size());
     assertEmpty("warning", getWarnings());
@@ -94,6 +103,7 @@
     return this;
   }
 
+  @Override
   public TestDiagnosticMessages assertOnlyWarnings() {
     assertEmpty("info", getInfos());
     assertNotEquals(0, getWarnings().size());
@@ -101,6 +111,7 @@
     return this;
   }
 
+  @Override
   public TestDiagnosticMessages assertOnlyErrors() {
     assertEmpty("info", getInfos());
     assertEmpty("warning", getWarnings());
@@ -108,16 +119,19 @@
     return this;
   }
 
+  @Override
   public TestDiagnosticMessages assertInfosCount(int count) {
     assertEquals(count, getInfos().size());
     return this;
   }
 
+  @Override
   public TestDiagnosticMessages assertWarningsCount(int count) {
     assertEquals(count, getWarnings().size());
     return this;
   }
 
+  @Override
   public TestDiagnosticMessages assertErrorsCount(int count) {
     assertEquals(count, getErrors().size());
     return this;
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 5c50407..c992015 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -171,7 +171,7 @@
   public static final Path R8LIB_EXCLUDE_DEPS_JAR = Paths.get(LIBS_DIR, "r8lib-exclude-deps.jar");
   public static final Path R8LIB_EXCLUDE_DEPS_MAP =
       Paths.get(LIBS_DIR, "r8lib-exclude-deps.jar.map");
-  public static final Path DEPS = Paths.get(LIBS_DIR, "deps.jar");
+  public static final Path DEPS = Paths.get(LIBS_DIR, "deps_all.jar");
 
   public static final Path DESUGAR_LIB_CONVERSIONS =
       Paths.get(LIBS_DIR, "library_desugar_conversions.zip");
diff --git a/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java b/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
index 07a33ab..c25dc40 100644
--- a/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
+++ b/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
@@ -116,11 +116,7 @@
   }
 
   private static int getVersion(CodeInspector inspector, Class<?> clazz) {
-    return inspector
-        .clazz(clazz)
-        .getDexProgramClass()
-        .asProgramClass()
-        .getInitialClassFileVersion();
+    return inspector.clazz(clazz).getDexProgramClass().getInitialClassFileVersion();
   }
 
   private static void checkVersion(CodeInspector inspector, Class<?> clazz, int version) {
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java
index 8769dc2..7cde792 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java
@@ -120,7 +120,7 @@
                 internalOptions -> {
                   if (parameters.isCfRuntime()) {
                     internalOptions.desugarState = DesugarState.ON;
-                    internalOptions.enableCfInterfaceMethodDesugaring = true;
+                    internalOptions.cfToCfDesugar = true;
                   }
                 });
     if (parameters.isDexRuntime()) {
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java
index ec742ee..e8ac332 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java
@@ -4,16 +4,11 @@
 
 package com.android.tools.r8.desugar;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.nio.file.Path;
 import org.junit.Test;
@@ -23,45 +18,43 @@
 @RunWith(Parameterized.class)
 public class DesugarToClassFile extends TestBase {
 
-  private final TestParameters parameters;
-
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
   }
 
+  private final TestParameters parameters;
+
   public DesugarToClassFile(TestParameters parameters) {
     this.parameters = parameters;
   }
 
-  private void checkSomething(CodeInspector inspector) {
-    ClassSubject classSubject = inspector.clazz(TestClass.class);
-    assertThat(classSubject, isPresent());
+  private void checkHasCompanionClass(CodeInspector inspector) {
+    assertTrue(
+        inspector.allClasses().stream()
+            .anyMatch(subject -> subject.getOriginalName().endsWith("$-CC")));
   }
 
-  private void checkDiagnostics(TestDiagnosticMessages messages) {
-    messages.assertOnlyWarnings();
-    messages.assertWarningsCount(1);
-    assertThat(
-        messages.getWarnings().get(0).getDiagnosticMessage(),
-        containsString("not officially supported"));
+  private void checkHasLambdaClass(CodeInspector inspector) {
+    assertTrue(
+        inspector.allClasses().stream()
+            .anyMatch(subject -> subject.getOriginalName().contains("-$$Lambda$")));
   }
 
   @Test
   public void test() throws Exception {
     // Use D8 to desugar with Java classfile output.
     Path jar =
-        testForD8()
+        testForD8(Backend.CF)
             .addInnerClasses(DesugarToClassFile.class)
             .setMinApi(parameters.getApiLevel())
-            .setOutputMode(OutputMode.ClassFile)
             .compile()
-            .inspectDiagnosticMessages(this::checkDiagnostics)
-            .inspect(this::checkSomething)
+            .inspect(this::checkHasCompanionClass)
+            .inspect(this::checkHasLambdaClass)
             .writeToZip();
 
     if (parameters.getRuntime().isCf()) {
-      // Run on the JVM
+      // Run on the JVM.
       testForJvm()
           .addProgramFiles(jar)
           .run(parameters.getRuntime(), TestClass.class)
@@ -71,8 +64,8 @@
       // Convert to DEX without desugaring.
       testForD8()
           .addProgramFiles(jar)
-          .setEnableDesugaring(false)
           .setMinApi(parameters.getApiLevel())
+          .setEnableDesugaring(false)
           .run(parameters.getRuntime(), TestClass.class)
           .assertSuccessWithOutputLines("Hello, world!", "I::foo");
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileInputCfVersion.java b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileInputCfVersion.java
new file mode 100644
index 0000000..0fd267d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileInputCfVersion.java
@@ -0,0 +1,85 @@
+// 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;
+
+import static org.objectweb.asm.Opcodes.V1_4;
+import static org.objectweb.asm.Opcodes.V1_5;
+import static org.objectweb.asm.Opcodes.V1_6;
+import static org.objectweb.asm.Opcodes.V1_7;
+import static org.objectweb.asm.Opcodes.V1_8;
+import static org.objectweb.asm.Opcodes.V9;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.google.common.collect.ImmutableList;
+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 DesugarToClassFileInputCfVersion extends TestBase {
+
+  @Parameters(name = "{0}, input Cf version: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+        ImmutableList.of(V1_4, V1_5, V1_6, V1_7, V1_8, V9));
+  }
+
+  private final TestParameters parameters;
+  private final int cfVersion;
+
+  public DesugarToClassFileInputCfVersion(TestParameters parameters, int cfVersion) {
+    this.parameters = parameters;
+    this.cfVersion = cfVersion;
+  }
+
+  @Test
+  public void test() throws Exception {
+    // Use D8 to desugar with Java classfile output.
+    Path jar =
+        testForD8(Backend.CF)
+            .addProgramClassFileData(transformer(TestClass.class).setVersion(cfVersion).transform())
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .writeToZip();
+
+    if (parameters.getRuntime().isCf()) {
+      // Run on the JVM given that Cf version is supported.
+      if (cfVersion <= parameters.getRuntime().asCf().getVm().getClassfileVersion()) {
+        testForJvm()
+            .addProgramFiles(jar)
+            .run(parameters.getRuntime(), TestClass.class)
+            .assertSuccessWithOutputLines("Hello, world!");
+      } else {
+        testForJvm()
+            .addProgramFiles(jar)
+            .run(parameters.getRuntime(), TestClass.class)
+            .assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class);
+      }
+    } else {
+      assert parameters.getRuntime().isDex();
+      // Convert to DEX without desugaring.
+      testForD8()
+          .addProgramFiles(jar)
+          .setMinApi(parameters.getApiLevel())
+          .setEnableDesugaring(false)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutputLines("Hello, world!");
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      if (System.currentTimeMillis() > 0) {
+        System.out.println("Hello, world!");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ApiLevelBackportsTest.java b/src/test/java/com/android/tools/r8/desugar/backports/ApiLevelBackportsTest.java
new file mode 100644
index 0000000..5dac547
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ApiLevelBackportsTest.java
@@ -0,0 +1,128 @@
+// 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.backports;
+
+import static org.hamcrest.core.StringContains.containsString;
+
+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.AndroidApiLevel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class ApiLevelBackportsTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimesStartingFromIncluding(Version.V9_0_0).build();
+  }
+
+  public ApiLevelBackportsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void backportSucceedsOnSupportedApiLevel() throws Exception {
+    testForD8()
+        .addProgramClassFileData(Dump.mainWithMathMultiplyExactLongInt())
+        .setMinApi(AndroidApiLevel.B)
+        .run(parameters.getRuntime(), "Test")
+        .assertSuccessWithOutputLines("4");
+  }
+
+  @Test
+  public void warningForNonPlatformBuild() throws Exception {
+    testForD8()
+        .addProgramClassFileData(Dump.mainWithMathMultiplyExactLongInt())
+        .setMinApi(30)
+        .compile()
+        .assertOnlyWarnings()
+        .assertWarningMessageThatMatches(
+            containsString("An API level of 30 is not supported by this compiler"))
+        .run(parameters.getRuntime(), "Test")
+        .assertFailureWithErrorThatMatches(
+            containsString("java.lang.NoSuchMethodError: No static method multiplyExact(JI)J"));
+  }
+
+  @Test
+  public void noWarningForPlatformBuild() throws Exception {
+    testForD8()
+        .addProgramClassFileData(Dump.mainWithMathMultiplyExactLongInt())
+        .setMinApi(AndroidApiLevel.magicApiLevelUsedByAndroidPlatformBuild)
+        .run(parameters.getRuntime(), "Test")
+        .assertFailureWithErrorThatMatches(
+            containsString("java.lang.NoSuchMethodError: No static method multiplyExact(JI)J"));
+  }
+
+  static class Dump implements Opcodes {
+
+    // Code for:
+    //
+    // class Test {
+    //   public static void main(String[] args) {
+    //     // Call Math.multiplyExact(long, int), which is not in Android Q.
+    //     System.out.println(Math.multiplyExact(2L, 2));
+    //   }
+    // }
+    //
+    static byte[] mainWithMathMultiplyExactLongInt() {
+
+      ClassWriter classWriter = new ClassWriter(0);
+      MethodVisitor methodVisitor;
+
+      classWriter.visit(V1_8, ACC_SUPER, "Test", null, "java/lang/Object", null);
+
+      classWriter.visitSource("Test.java", null);
+
+      {
+        methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(1, label0);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(1, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor =
+            classWriter.visitMethod(
+                ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(3, label0);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitLdcInsn(Long.valueOf(2L));
+        methodVisitor.visitLdcInsn(Integer.valueOf(2));
+        methodVisitor.visitMethodInsn(
+            INVOKESTATIC, "java/lang/Math", "multiplyExact", "(JI)J", false);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V", false);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLineNumber(4, label1);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(5, 1);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/NoBackportForAndroidPlatform.java b/src/test/java/com/android/tools/r8/desugar/backports/NoBackportForAndroidPlatform.java
deleted file mode 100644
index 47081ab..0000000
--- a/src/test/java/com/android/tools/r8/desugar/backports/NoBackportForAndroidPlatform.java
+++ /dev/null
@@ -1,128 +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.desugar.backports;
-
-import static org.hamcrest.core.StringContains.containsString;
-
-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.objectweb.asm.ClassWriter;
-import org.objectweb.asm.Label;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-
-@RunWith(Parameterized.class)
-public class NoBackportForAndroidPlatform extends TestBase implements Opcodes {
-
-  private final TestParameters parameters;
-
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    // The use of high API level will produce dex files with high DEX version, so only run on high
-    // API level VMs.
-    return getTestParameters()
-        .withDexRuntimes()
-        .withApiLevelsStartingAtIncluding(AndroidApiLevel.P)
-        .build();
-  }
-
-  public NoBackportForAndroidPlatform(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  @Test
-  public void backportSucceedsOnSupportedApiLevel() throws Exception {
-    testForD8()
-        .addProgramClassFileData(mainWithMathMultiplyExactLongInt())
-        .setMinApi(AndroidApiLevel.B)
-        .run(parameters.getRuntime(), "Test")
-        .assertSuccessWithOutputLines("4");
-  }
-
-  @Test
-  public void warningForNonPlatformBuild() throws Exception {
-    testForD8()
-        .addProgramClassFileData(mainWithMathMultiplyExactLongInt())
-        .setMinApi(30)
-        .compile()
-        .assertOnlyWarnings()
-        .assertWarningMessageThatMatches(
-            containsString("An API level of 30 is not supported by this compiler"))
-        .run(parameters.getRuntime(), "Test")
-        .assertFailureWithErrorThatMatches(
-            containsString("java.lang.NoSuchMethodError: No static method multiplyExact(JI)J"));
-  }
-
-  @Test
-  public void noWarningForPlatformBuild() throws Exception {
-    testForD8()
-        .addProgramClassFileData(mainWithMathMultiplyExactLongInt())
-        .setMinApi(AndroidApiLevel.magicApiLevelUsedByAndroidPlatformBuild)
-        .run(parameters.getRuntime(), "Test")
-        .assertFailureWithErrorThatMatches(
-            containsString("java.lang.NoSuchMethodError: No static method multiplyExact(JI)J"));
-  }
-
-  // Code for:
-  //
-  // class Test {
-  //   public static void main(String[] args) {
-  //     // Call Math.multiplyExact(long, int), which is not in Android Q.
-  //     System.out.println(Math.multiplyExact(2L, 2));
-  //   }
-  // }
-  //
-  private byte[] mainWithMathMultiplyExactLongInt() {
-
-    ClassWriter classWriter = new ClassWriter(0);
-    MethodVisitor methodVisitor;
-
-    classWriter.visit(V1_8, ACC_SUPER, "Test", null, "java/lang/Object", null);
-
-    classWriter.visitSource("Test.java", null);
-
-    {
-      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(1, label0);
-      methodVisitor.visitVarInsn(ALOAD, 0);
-      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
-      methodVisitor.visitInsn(RETURN);
-      methodVisitor.visitMaxs(1, 1);
-      methodVisitor.visitEnd();
-    }
-    {
-      methodVisitor =
-          classWriter.visitMethod(
-              ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
-      methodVisitor.visitCode();
-      Label label0 = new Label();
-      methodVisitor.visitLabel(label0);
-      methodVisitor.visitLineNumber(3, label0);
-      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
-      methodVisitor.visitLdcInsn(Long.valueOf(2L));
-      methodVisitor.visitLdcInsn(Integer.valueOf(2));
-      methodVisitor.visitMethodInsn(
-          INVOKESTATIC, "java/lang/Math", "multiplyExact", "(JI)J", false);
-      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V", false);
-      Label label1 = new Label();
-      methodVisitor.visitLabel(label1);
-      methodVisitor.visitLineNumber(4, label1);
-      methodVisitor.visitInsn(RETURN);
-      methodVisitor.visitMaxs(5, 1);
-      methodVisitor.visitEnd();
-    }
-    classWriter.visitEnd();
-
-    return classWriter.toByteArray();
-  }
-}
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 bf823d4..6177bf3 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
@@ -19,10 +19,10 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
@@ -89,10 +89,11 @@
     DexProgramClass clazz = appView.appInfo().classes().iterator().next();
     assertEquals(TestClass.class.getTypeName(), clazz.type.toSourceString());
 
-    for (DexEncodedMethod method : clazz.methods()) {
-      IRCode code = method.buildIR(appView, Origin.unknown());
-      fieldAccessAnalysis.recordFieldAccesses(code, feedback, new MethodProcessorMock());
-    }
+    clazz.forEachProgramMethod(
+        method -> {
+          IRCode code = method.buildIR(appView);
+          fieldAccessAnalysis.recordFieldAccesses(code, feedback, new MethodProcessorMock());
+        });
 
     int bitsReadInBitField = feedback.bitsReadPerField.getInt(uniqueFieldByName(clazz, "bitField"));
     assertTrue(BitUtils.isBitSet(bitsReadInBitField, 1));
@@ -216,12 +217,12 @@
     }
 
     @Override
-    public boolean shouldApplyCodeRewritings(DexEncodedMethod method) {
+    public boolean shouldApplyCodeRewritings(ProgramMethod method) {
       return false;
     }
 
     @Override
-    public boolean isProcessedConcurrently(DexEncodedMethod method) {
+    public boolean isProcessedConcurrently(ProgramMethod method) {
       return false;
     }
   }
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
index 7b7036f..2d56233 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
@@ -4,30 +4,59 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedField;
 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.DexTypeList;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
+import com.android.tools.r8.origin.SynthesizedOrigin;
+import java.util.Collections;
 
 class CallGraphTestBase extends TestBase {
+
   private DexItemFactory dexItemFactory = new DexItemFactory();
+  private DexProgramClass clazz =
+      new DexProgramClass(
+          dexItemFactory.createType("LCallGraphTest;"),
+          null,
+          new SynthesizedOrigin("test", CallGraphTestBase.class),
+          ClassAccessFlags.fromSharedAccessFlags(0),
+          dexItemFactory.objectType,
+          DexTypeList.empty(),
+          null,
+          null,
+          Collections.emptyList(),
+          null,
+          Collections.emptyList(),
+          DexAnnotationSet.empty(),
+          DexEncodedField.EMPTY_ARRAY,
+          DexEncodedField.EMPTY_ARRAY,
+          DexEncodedMethod.EMPTY_ARRAY,
+          DexEncodedMethod.EMPTY_ARRAY,
+          false,
+          DexProgramClass::invalidChecksumRequest);
 
   Node createNode(String methodName) {
     DexMethod signature =
         dexItemFactory.createMethod(
-            dexItemFactory.objectType,
-            dexItemFactory.createProto(dexItemFactory.voidType),
-            methodName);
-    return new Node(
-        new DexEncodedMethod(
-            signature, null, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), null));
+            clazz.type, dexItemFactory.createProto(dexItemFactory.voidType), methodName);
+    ProgramMethod method =
+        new ProgramMethod(
+            clazz,
+            new DexEncodedMethod(
+                signature, null, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), null));
+    return new Node(method);
   }
 
   Node createForceInlinedNode(String methodName) {
     Node node = createNode(methodName);
-    node.method.getMutableOptimizationInfo().markForceInline();
+    node.getMethod().getMutableOptimizationInfo().markForceInline();
     return node;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/CycleEliminationTest.java b/src/test/java/com/android/tools/r8/ir/conversion/CycleEliminationTest.java
index c9ae9fd..2611dcd 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/CycleEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/CycleEliminationTest.java
@@ -161,9 +161,9 @@
 
       for (Node node : configuration.nodes) {
         if (configuration.forceInline.contains(node)) {
-          node.method.getMutableOptimizationInfo().markForceInline();
+          node.getMethod().getMutableOptimizationInfo().markForceInline();
         } else {
-          node.method.getMutableOptimizationInfo().unsetForceInline();
+          node.getMethod().getMutableOptimizationInfo().unsetForceInline();
         }
       }
 
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java b/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
index 4088693..1459b20 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/NodeExtractionTest.java
@@ -47,20 +47,20 @@
     nodes.add(n6);
 
     CallGraph cg = new CallGraph(nodes);
-    Set<DexEncodedMethod> wave = cg.extractLeaves();
+    Set<DexEncodedMethod> wave = cg.extractLeaves().toDefinitionSet();
     assertEquals(3, wave.size());
-    assertThat(wave, hasItem(n3.method));
-    assertThat(wave, hasItem(n4.method));
-    assertThat(wave, hasItem(n6.method));
+    assertThat(wave, hasItem(n3.getMethod()));
+    assertThat(wave, hasItem(n4.getMethod()));
+    assertThat(wave, hasItem(n6.getMethod()));
 
-    wave = cg.extractLeaves();
+    wave = cg.extractLeaves().toDefinitionSet();
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n2.method));
-    assertThat(wave, hasItem(n5.method));
+    assertThat(wave, hasItem(n2.getMethod()));
+    assertThat(wave, hasItem(n5.getMethod()));
 
-    wave = cg.extractLeaves();
+    wave = cg.extractLeaves().toDefinitionSet();
     assertEquals(1, wave.size());
-    assertThat(wave, hasItem(n1.method));
+    assertThat(wave, hasItem(n1.getMethod()));
     assertTrue(nodes.isEmpty());
   }
 
@@ -91,27 +91,27 @@
     nodes.add(n6);
 
     n1.addCallerConcurrently(n3);
-    n3.method.getMutableOptimizationInfo().markForceInline();
+    n3.getMethod().getMutableOptimizationInfo().markForceInline();
     CycleEliminator cycleEliminator = new CycleEliminator();
     assertEquals(1, cycleEliminator.breakCycles(nodes).numberOfRemovedCallEdges());
 
     CallGraph cg = new CallGraph(nodes);
-    Set<DexEncodedMethod> wave = cg.extractLeaves();
+    Set<DexEncodedMethod> wave = cg.extractLeaves().toDefinitionSet();
     assertEquals(3, wave.size());
-    assertThat(wave, hasItem(n3.method));
-    assertThat(wave, hasItem(n4.method));
-    assertThat(wave, hasItem(n6.method));
+    assertThat(wave, hasItem(n3.getMethod()));
+    assertThat(wave, hasItem(n4.getMethod()));
+    assertThat(wave, hasItem(n6.getMethod()));
     wave.clear();
 
-    wave = cg.extractLeaves();
+    wave = cg.extractLeaves().toDefinitionSet();
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n2.method));
-    assertThat(wave, hasItem(n5.method));
+    assertThat(wave, hasItem(n2.getMethod()));
+    assertThat(wave, hasItem(n5.getMethod()));
     wave.clear();
 
-    wave = cg.extractLeaves();
+    wave = cg.extractLeaves().toDefinitionSet();
     assertEquals(1, wave.size());
-    assertThat(wave, hasItem(n1.method));
+    assertThat(wave, hasItem(n1.getMethod()));
     assertTrue(nodes.isEmpty());
   }
 
@@ -142,20 +142,20 @@
     nodes.add(n6);
 
     CallGraph callGraph = new CallGraph(nodes, null);
-    Set<DexEncodedMethod> wave = callGraph.extractRoots();
+    Set<DexEncodedMethod> wave = callGraph.extractRoots().toDefinitionSet();
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n1.method));
-    assertThat(wave, hasItem(n5.method));
+    assertThat(wave, hasItem(n1.getMethod()));
+    assertThat(wave, hasItem(n5.getMethod()));
 
-    wave = callGraph.extractRoots();
+    wave = callGraph.extractRoots().toDefinitionSet();
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n2.method));
-    assertThat(wave, hasItem(n6.method));
+    assertThat(wave, hasItem(n2.getMethod()));
+    assertThat(wave, hasItem(n6.getMethod()));
 
-    wave = callGraph.extractRoots();
+    wave = callGraph.extractRoots().toDefinitionSet();
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n3.method));
-    assertThat(wave, hasItem(n4.method));
+    assertThat(wave, hasItem(n3.getMethod()));
+    assertThat(wave, hasItem(n4.getMethod()));
     assertTrue(nodes.isEmpty());
   }
 
@@ -186,25 +186,25 @@
     nodes.add(n6);
 
     n1.addCallerConcurrently(n3);
-    n3.method.getMutableOptimizationInfo().markForceInline();
+    n3.getMethod().getMutableOptimizationInfo().markForceInline();
     CycleEliminator cycleEliminator = new CycleEliminator();
     assertEquals(1, cycleEliminator.breakCycles(nodes).numberOfRemovedCallEdges());
 
     CallGraph callGraph = new CallGraph(nodes, null);
-    Set<DexEncodedMethod> wave = callGraph.extractRoots();
+    Set<DexEncodedMethod> wave = callGraph.extractRoots().toDefinitionSet();
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n1.method));
-    assertThat(wave, hasItem(n5.method));
+    assertThat(wave, hasItem(n1.getMethod()));
+    assertThat(wave, hasItem(n5.getMethod()));
 
-    wave = callGraph.extractRoots();
+    wave = callGraph.extractRoots().toDefinitionSet();
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n2.method));
-    assertThat(wave, hasItem(n6.method));
+    assertThat(wave, hasItem(n2.getMethod()));
+    assertThat(wave, hasItem(n6.getMethod()));
 
-    wave = callGraph.extractRoots();
+    wave = callGraph.extractRoots().toDefinitionSet();
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(n3.method));
-    assertThat(wave, hasItem(n4.method));
+    assertThat(wave, hasItem(n3.getMethod()));
+    assertThat(wave, hasItem(n4.getMethod()));
     assertTrue(nodes.isEmpty());
   }
 }
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 1ea4e67..c838a0d 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
@@ -11,8 +11,9 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
@@ -21,8 +22,8 @@
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
@@ -66,37 +67,41 @@
     assertNotNull(m5);
     assertNotNull(m6);
 
-    Set<DexEncodedMethod> wave = cg.extractLeaves();
+    Set<DexEncodedMethod> wave = cg.extractLeaves().toDefinitionSet();
     assertEquals(4, wave.size()); // including <init>
-    assertThat(wave, hasItem(m3.method));
-    assertThat(wave, hasItem(m4.method));
-    assertThat(wave, hasItem(m6.method));
+    assertThat(wave, hasItem(m3.getMethod()));
+    assertThat(wave, hasItem(m4.getMethod()));
+    assertThat(wave, hasItem(m6.getMethod()));
 
-    wave = cg.extractLeaves();
+    wave = cg.extractLeaves().toDefinitionSet();
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(m2.method));
-    assertThat(wave, hasItem(m5.method));
+    assertThat(wave, hasItem(m2.getMethod()));
+    assertThat(wave, hasItem(m5.getMethod()));
 
-    wave = cg.extractLeaves();
+    wave = cg.extractLeaves().toDefinitionSet();
     assertEquals(1, wave.size());
-    assertThat(wave, hasItem(m1.method));
+    assertThat(wave, hasItem(m1.getMethod()));
     assertTrue(cg.nodes.isEmpty());
   }
 
   @Test
   public void testPartialGraph() throws Exception {
-    DexEncodedMethod em1 = findMethod("m1");
-    DexEncodedMethod em2 = findMethod("m2");
-    DexEncodedMethod em4 = findMethod("m4");
-    DexEncodedMethod em5 = findMethod("m5");
+    ProgramMethod em1 = findMethod("m1");
+    ProgramMethod em2 = findMethod("m2");
+    ProgramMethod em4 = findMethod("m4");
+    ProgramMethod em5 = findMethod("m5");
     assertNotNull(em1);
     assertNotNull(em2);
     assertNotNull(em4);
     assertNotNull(em5);
 
+    ProgramMethodSet seeds = ProgramMethodSet.create();
+    seeds.add(em1);
+    seeds.add(em2);
+    seeds.add(em4);
+    seeds.add(em5);
     CallGraph pg =
-        new PartialCallGraphBuilder(appView, ImmutableSet.of(em1, em2, em4, em5))
-            .build(executorService, Timing.empty());
+        new PartialCallGraphBuilder(appView, seeds).build(executorService, Timing.empty());
 
     Node m1 = findNode(pg.nodes, "m1");
     Node m2 = findNode(pg.nodes, "m2");
@@ -109,37 +114,37 @@
 
     Set<DexEncodedMethod> wave = Sets.newIdentityHashSet();
 
-    wave.addAll(pg.extractRoots());
+    wave.addAll(pg.extractRoots().toDefinitionSet());
     assertEquals(2, wave.size());
-    assertThat(wave, hasItem(m1.method));
-    assertThat(wave, hasItem(m5.method));
+    assertThat(wave, hasItem(m1.getMethod()));
+    assertThat(wave, hasItem(m5.getMethod()));
     wave.clear();
 
-    wave.addAll(pg.extractRoots());
+    wave.addAll(pg.extractRoots().toDefinitionSet());
     assertEquals(1, wave.size());
-    assertThat(wave, hasItem(m2.method));
+    assertThat(wave, hasItem(m2.getMethod()));
     wave.clear();
 
-    wave.addAll(pg.extractRoots());
+    wave.addAll(pg.extractRoots().toDefinitionSet());
     assertEquals(1, wave.size());
-    assertThat(wave, hasItem(m4.method));
+    assertThat(wave, hasItem(m4.getMethod()));
     assertTrue(pg.nodes.isEmpty());
   }
 
   private Node findNode(Iterable<Node> nodes, String name) {
     for (Node n : nodes) {
-      if (n.method.method.name.toString().equals(name)) {
+      if (n.getMethod().method.name.toString().equals(name)) {
         return n;
       }
     }
     return null;
   }
 
-  private DexEncodedMethod findMethod(String name) {
-    for (DexClass clazz : appView.appInfo().classes()) {
+  private ProgramMethod findMethod(String name) {
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
       for (DexEncodedMethod method : clazz.methods()) {
         if (method.method.name.toString().equals(name)) {
-          return method;
+          return new ProgramMethod(clazz, method);
         }
       }
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java
index b5997bf..0c158b9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -41,11 +41,11 @@
         .assertSuccessWithOutputLines("10");
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
     // TODO(b/139246447): should avoid visiting A#<init>, which is trivial, default init!
-    assert encodedMethod.holder().toSourceString().endsWith("A")
-            && encodedMethod.toSourceString().contains("<init>")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
+    assert method.getHolderType().toSourceString().endsWith("A")
+            && method.toSourceString().contains("<init>")
+        : "Unexpected revisit: " + method.toSourceString();
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
index 54a3b8e..2edbb0b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -60,11 +60,12 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert encodedMethod.method.name.toString().equals("m")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
-    if (encodedMethod.holder().toSourceString().endsWith("$C")) {
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert method.getReference().name.toString().equals("m")
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
+    if (method.getHolderType().toSourceString().endsWith("$C")) {
       assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
     } else {
       assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNull();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java
index 96b85ec..e36941d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -57,11 +57,12 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert encodedMethod.method.name.toString().equals("m")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
-    if (encodedMethod.holder().toSourceString().endsWith("$C")) {
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert method.getReference().name.toString().equals("m")
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
+    if (method.getHolderType().toSourceString().endsWith("$C")) {
       assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
     } else {
       assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNull();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/KeptMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/KeptMethodTest.java
index 51797eb..8d405c4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/KeptMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/KeptMethodTest.java
@@ -12,7 +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.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -55,8 +55,8 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert false : "Unexpected revisit: " + encodedMethod.toSourceString();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert false : "Unexpected revisit: " + method.toSourceString();
   }
 
   private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java
index 19172e7..8a057dd 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/LibraryMethodOverridesTest.java
@@ -13,7 +13,7 @@
 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.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -66,8 +66,8 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert false : "Unexpected revisit: " + encodedMethod.toSourceString();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert false : "Unexpected revisit: " + method.toSourceString();
   }
 
   private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectNegativeTest.java
index 19a9034..2e76fa2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectNegativeTest.java
@@ -12,7 +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.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -55,10 +55,11 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert encodedMethod.method.name.toString().equals("test")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert method.getReference().name.toString().equals("test")
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
     assert callSiteOptimizationInfo.getAbstractArgumentValue(1).isUnknown();
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
index 30aec5e..bdc3694 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
@@ -12,7 +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.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.InternalOptions;
@@ -58,11 +58,11 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert encodedMethod.method.name.toString().equals("test")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
-    assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert method.getReference().name.toString().equals("test")
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     AbstractValue abstractValue = callSiteOptimizationInfo.getAbstractArgumentValue(1);
     assert abstractValue.isSingleStringValue()
         && abstractValue.asSingleStringValue().getDexString().toString().equals("nul");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java
index ba6b644..75dd8a8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -60,10 +60,11 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert encodedMethod.method.name.toString().equals("m")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert method.getReference().name.toString().equals("m")
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
     assert callSiteOptimizationInfo.getAbstractArgumentValue(1).isUnknown();
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
index a966e62..e837610 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
@@ -12,7 +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.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.InternalOptions;
@@ -61,10 +61,11 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert encodedMethod.method.name.toString().equals("m")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert method.getReference().name.toString().equals("m")
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
     AbstractValue abstractValue = callSiteOptimizationInfo.getAbstractArgumentValue(1);
     assert abstractValue.isSingleStringValue()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticNegativeTest.java
index cacb6a3..df1fb98 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticNegativeTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -52,10 +52,11 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert encodedMethod.method.name.toString().equals("test")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert method.getReference().name.toString().equals("test")
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     assert callSiteOptimizationInfo.getDynamicUpperBoundType(0).isDefinitelyNotNull();
     assert callSiteOptimizationInfo.getAbstractArgumentValue(0).isUnknown();
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
index 1d92c6e..a1e936b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.InternalOptions;
@@ -55,10 +55,11 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert encodedMethod.method.name.toString().equals("test")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert method.getReference().name.toString().equals("test")
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     assert callSiteOptimizationInfo.getDynamicUpperBoundType(0).isDefinitelyNotNull();
     AbstractValue abstractValue = callSiteOptimizationInfo.getAbstractArgumentValue(0);
     assert abstractValue.isSingleStringValue()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java
index 28523aa..3ae302a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -57,11 +57,12 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    String methodName = encodedMethod.method.name.toString();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    String methodName = method.getReference().name.toString();
     assert methodName.equals("m") || methodName.equals("test")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     if (methodName.equals("m")) {
       assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
       assert callSiteOptimizationInfo.getAbstractArgumentValue(1).isUnknown();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
index 14ff937..30764845 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.InternalOptions;
@@ -59,13 +59,14 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert encodedMethod.method.name.toString().equals("m")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert method.getReference().name.toString().equals("m")
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
     AbstractValue abstractValue = callSiteOptimizationInfo.getAbstractArgumentValue(1);
-    if (encodedMethod.holder().toSourceString().endsWith("$A")) {
+    if (method.getHolderType().toSourceString().endsWith("$A")) {
       assert abstractValue.isSingleStringValue()
           && abstractValue.asSingleStringValue().getDexString().toString().equals("nul");
     } else {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java
index 349c613..0cc9bb1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java
@@ -12,7 +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.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -55,10 +55,11 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert encodedMethod.method.name.toString().equals("test")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert method.getReference().name.toString().equals("test")
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     TypeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
     assert upperBoundType.isDefinitelyNotNull();
     assert upperBoundType.isClassType()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
index 9119e88..e131be5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -57,11 +57,12 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    String methodName = encodedMethod.method.name.toString();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    String methodName = method.getReference().name.toString();
     assert methodName.equals("<init>") || methodName.equals("test")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     TypeElement upperBoundType;
     if (methodName.equals("test")) {
       upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
index b341d62..4b4c534 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -60,10 +60,11 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert encodedMethod.method.name.toString().equals("m")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert method.getReference().name.toString().equals("m")
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     TypeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
     assert upperBoundType.isDefinitelyNotNull();
     assert upperBoundType.isClassType()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
index 204f41b..bda0a3a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -60,13 +60,14 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert encodedMethod.method.name.toString().equals("m")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert method.getReference().name.toString().equals("m")
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     TypeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
     assert upperBoundType.isDefinitelyNotNull();
-    if (encodedMethod.holder().toSourceString().endsWith("$A")) {
+    if (method.getHolderType().toSourceString().endsWith("$A")) {
       assert upperBoundType.isClassType()
           && upperBoundType.asClassType().getClassType().toSourceString().endsWith("$Sub1");
     } else {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java
index d98e911..3c94778 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -52,10 +52,11 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert encodedMethod.method.name.toString().equals("test")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert method.getReference().name.toString().equals("test")
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     TypeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(0);
     assert upperBoundType.isDefinitelyNotNull();
     assert upperBoundType.isClassType()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
index d014d5a..6ea0888 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
@@ -12,7 +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.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -54,11 +54,12 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    String methodName = encodedMethod.method.name.toString();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    String methodName = method.getReference().name.toString();
     assert methodName.equals("<init>") || methodName.equals("test")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     // `arg` for `test` or the receiver of `Base#<init>`.
     // TODO(b/139246447): should avoid visiting <init>, which is trivial, default init!
     // For testing purpose, `Base` is not merged and kept. The system correctly caught that, when
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
index c2fffc1..51145b2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -57,11 +57,12 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    String methodName = encodedMethod.method.name.toString();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    String methodName = method.getReference().name.toString();
     assert methodName.equals("m") || methodName.equals("test")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     if (methodName.equals("m")) {
       TypeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
       assert upperBoundType.isDefinitelyNotNull();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
index 22a4faa..8c17c3a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -57,11 +57,12 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    String methodName = encodedMethod.method.name.toString();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    String methodName = method.getReference().name.toString();
     assert methodName.equals("<init>") || methodName.equals("m")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     TypeElement upperBoundType;
     if (methodName.equals("m")) {
       upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java
index 5b79156..96614da 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java
@@ -12,7 +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.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -53,8 +53,8 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert false : "Unexpected revisit: " + encodedMethod.toSourceString();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert false : "Unexpected revisit: " + method.toSourceString();
   }
 
   private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java
index 9630bab..0b7ff7d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java
@@ -12,7 +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.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -54,10 +54,11 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert encodedMethod.method.name.toString().equals("test")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert method.getReference().name.toString().equals("test")
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
index e7c5597..79a49e0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -60,10 +60,11 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert encodedMethod.method.name.toString().equals("m")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert method.getReference().name.toString().equals("m")
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     TypeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
     assert upperBoundType.isNullable();
     assert upperBoundType.isClassType()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
index 17cea42..27612a2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
@@ -12,7 +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.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -57,10 +57,11 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert encodedMethod.method.name.toString().equals("m")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert method.getReference().name.toString().equals("m")
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     assert callSiteOptimizationInfo.getDynamicUpperBoundType(1).isDefinitelyNotNull();
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticNegativeTest.java
index 1f3d6cc..0a18b69 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticNegativeTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -50,8 +50,8 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert false : "Unexpected revisit: " + encodedMethod.toSourceString();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert false : "Unexpected revisit: " + method.toSourceString();
   }
 
   private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java
index 4743089..700ec34 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -51,10 +51,11 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert encodedMethod.method.name.toString().equals("test")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert method.getReference().name.toString().equals("test")
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     assert callSiteOptimizationInfo.getDynamicUpperBoundType(0).isDefinitelyNotNull();
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
index 4882c32..26bb65e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -57,16 +57,17 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    String methodName = encodedMethod.method.name.toString();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    String methodName = method.getReference().name.toString();
     assert methodName.equals("m") || methodName.equals("test")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     if (methodName.equals("m")) {
       TypeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
       assert upperBoundType.isNullable();
       assert upperBoundType.isClassType()
-          && upperBoundType.asClassType().getClassType().equals(encodedMethod.holder());
+          && upperBoundType.asClassType().getClassType().equals(method.getHolderType());
     } else {
       assert methodName.equals("test");
       assert callSiteOptimizationInfo.getDynamicUpperBoundType(0).isDefinitelyNotNull();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
index 0009024..00601a9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -57,17 +57,18 @@
         .inspect(this::inspect);
   }
 
-  private void callSiteOptimizationInfoInspect(DexEncodedMethod encodedMethod) {
-    assert encodedMethod.method.name.toString().equals("m")
-        : "Unexpected revisit: " + encodedMethod.toSourceString();
-    CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
+  private void callSiteOptimizationInfoInspect(ProgramMethod method) {
+    assert method.getReference().name.toString().equals("m")
+        : "Unexpected revisit: " + method.toSourceString();
+    CallSiteOptimizationInfo callSiteOptimizationInfo =
+        method.getDefinition().getCallSiteOptimizationInfo();
     TypeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
     assert upperBoundType.isClassType()
         && upperBoundType.asClassType().getClassType().toSourceString().endsWith("$A");
-    if (encodedMethod.holder().toSourceString().endsWith("$A")) {
+    if (method.getHolderType().toSourceString().endsWith("$A")) {
       assert upperBoundType.isDefinitelyNotNull();
     } else {
-      assert encodedMethod.holder().toSourceString().endsWith("$B");
+      assert method.getHolderType().toSourceString().endsWith("$B");
       assert upperBoundType.isNullable();
     }
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWithDefaultValueAssignmentAfterDefaultsOptimizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWithDefaultValueAssignmentAfterDefaultsOptimizationTest.java
index df33e2b..8943139 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWithDefaultValueAssignmentAfterDefaultsOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWithDefaultValueAssignmentAfterDefaultsOptimizationTest.java
@@ -10,9 +10,8 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import java.util.ArrayList;
-import java.util.Collection;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.Deque;
 import java.util.Optional;
 import org.junit.Test;
@@ -46,16 +45,16 @@
         .assertSuccessWithOutputLines("42");
   }
 
-  private void waveModifier(Deque<Collection<DexEncodedMethod>> waves) {
-    Collection<DexEncodedMethod> initialWave = waves.getFirst();
-    Optional<DexEncodedMethod> printFieldMethod =
+  private void waveModifier(Deque<ProgramMethodSet> waves) {
+    ProgramMethodSet initialWave = waves.getFirst();
+    Optional<ProgramMethod> printFieldMethod =
         initialWave.stream()
-            .filter(method -> method.method.name.toSourceString().equals("printField"))
+            .filter(method -> method.getReference().name.toSourceString().equals("printField"))
             .findFirst();
     assertTrue(printFieldMethod.isPresent());
-    initialWave.remove(printFieldMethod.get());
+    initialWave.remove(printFieldMethod.get().getDefinition());
 
-    ArrayList<DexEncodedMethod> lastWave = new ArrayList<>();
+    ProgramMethodSet lastWave = ProgramMethodSet.create();
     lastWave.add(printFieldMethod.get());
     waves.addLast(lastWave);
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java
index d230621..b493f1d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldWriteBeforeFieldReadTest.java
@@ -15,11 +15,10 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import java.util.Collection;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import org.junit.Test;
@@ -50,13 +49,12 @@
             options -> {
               options.testing.waveModifier =
                   (waves) -> {
-                    Function<String, Predicate<Collection<DexEncodedMethod>>> wavePredicate =
+                    Function<String, Predicate<ProgramMethodSet>> wavePredicate =
                         methodName ->
                             wave ->
                                 wave.stream()
                                     .anyMatch(
-                                        method ->
-                                            method.method.toSourceString().contains(methodName));
+                                        method -> method.toSourceString().contains(methodName));
                     int readFieldsWaveIndex =
                         IterableUtils.firstIndexMatching(waves, wavePredicate.apply("readFields"));
                     assertTrue(readFieldsWaveIndex >= 0);
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
index fcb5958..32a0e6b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.kotlin.KotlinMetadataWriter;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -77,13 +78,12 @@
       assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
       // We cannot assert equality of the data since it may be ordered differently. Instead we use
       // the KotlinMetadataWriter.
-      // TODO(b/155571455): Deactivating the method call to kotlinMetadataString until resolved.
-      // String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
-      // String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata);
-      // // TODO(b/155534905): For invalid synthetic class lambdas, we emit null after rewriting.
-      // if (clazzSubject.getKotlinClassMetadata().getHeader().getKind() != 3) {
-      //   assertEquals(expected, actual);
-      // }
+      String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
+      String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata);
+      // TODO(b/155534905): For invalid synthetic class lambdas, we emit null after rewriting.
+      if (clazzSubject.getKotlinClassMetadata().getHeader().getKind() != 3) {
+        assertEquals(expected, actual);
+      }
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 2b5270d..cacf66b 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -50,6 +50,7 @@
 import com.android.tools.r8.graph.InitClassLens;
 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.CatchHandlers;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Position;
@@ -83,6 +84,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -806,10 +808,31 @@
     options.intermediate = intermediate;
     DexItemFactory factory = options.itemFactory;
     AppInfo appInfo = new AppInfo(DexApplication.builder(options, timing).build());
-    DexApplication.Builder builder = DexApplication.builder(options, timing);
+    AppView<?> appView = AppView.createForR8(appInfo, options);
+    DexApplication.Builder<?> builder = DexApplication.builder(options, timing);
     for (String clazz : classes) {
       DexString desc = factory.createString(DescriptorUtils.javaTypeToDescriptor(clazz));
       DexType type = factory.createType(desc);
+      DexProgramClass programClass =
+          new DexProgramClass(
+              type,
+              null,
+              new SynthesizedOrigin("test", MainDexListTests.class),
+              ClassAccessFlags.fromSharedAccessFlags(0),
+              factory.objectType,
+              DexTypeList.empty(),
+              null,
+              null,
+              Collections.emptyList(),
+              null,
+              Collections.emptyList(),
+              DexAnnotationSet.empty(),
+              DexEncodedField.EMPTY_ARRAY,
+              DexEncodedField.EMPTY_ARRAY,
+              DexEncodedMethod.EMPTY_ARRAY,
+              DexEncodedMethod.EMPTY_ARRAY,
+              false,
+              DexProgramClass::invalidChecksumRequest);
       DexEncodedMethod[] directMethods = new DexEncodedMethod[methodCount];
       for (int i = 0; i < methodCount; i++) {
         MethodAccessFlags access = MethodAccessFlags.fromSharedAccessFlags(0, false);
@@ -831,32 +854,13 @@
                 DexAnnotationSet.empty(),
                 ParameterAnnotationsList.empty(),
                 code);
-        AppView<?> appView = AppView.createForR8(appInfo, options);
-        IRCode ir = code.buildIR(method, appView, Origin.unknown());
+        ProgramMethod programMethod = new ProgramMethod(programClass, method);
+        IRCode ir = code.buildIR(programMethod, appView, Origin.unknown());
         RegisterAllocator allocator = new LinearScanRegisterAllocator(appView, ir);
         method.setCode(ir, allocator, appView);
         directMethods[i] = method;
       }
-      DexProgramClass programClass =
-          new DexProgramClass(
-              type,
-              null,
-              new SynthesizedOrigin("test", MainDexListTests.class),
-              ClassAccessFlags.fromSharedAccessFlags(0),
-              factory.objectType,
-              DexTypeList.empty(),
-              null,
-              null,
-              Collections.emptyList(),
-              null,
-              Collections.emptyList(),
-              DexAnnotationSet.empty(),
-              DexEncodedField.EMPTY_ARRAY,
-              DexEncodedField.EMPTY_ARRAY,
-              directMethods,
-              DexEncodedMethod.EMPTY_ARRAY,
-              false,
-              DexProgramClass::invalidChecksumRequest);
+      programClass.getMethodCollection().addDirectMethods(Arrays.asList(directMethods));
       builder.addProgramClass(programClass);
     }
     DirectMappedDexApplication application = builder.build().toDirect();
diff --git a/src/test/java/com/android/tools/r8/relocator/RelocatorServiceLoaderTest.java b/src/test/java/com/android/tools/r8/relocator/RelocatorServiceLoaderTest.java
index 96d429c..759082f 100644
--- a/src/test/java/com/android/tools/r8/relocator/RelocatorServiceLoaderTest.java
+++ b/src/test/java/com/android/tools/r8/relocator/RelocatorServiceLoaderTest.java
@@ -48,7 +48,7 @@
   public RelocatorServiceLoaderTest(TestParameters parameters) {}
 
   @Test
-  public void testNotRewritingServiceForNotFoundClass()
+  public void testRewritingOfServicesForNotFoundClasses()
       throws IOException, CompilationFailedException, ResourceException {
     File testJar = temp.newFile("test.jar");
     Path testJarPath = testJar.toPath();
@@ -72,8 +72,13 @@
                 Reference.packageFromString("foo.bar"), Reference.packageFromString("baz.qux"))
             .build());
     zip = new ZipFile(relocatedJar.toFile());
-    ZipEntry serviceEntry = zip.getEntry(SERVICE_FILE);
+    ZipEntry serviceEntry = zip.getEntry("META-INF/services/baz.qux.Baz");
     assertNotNull(serviceEntry);
+    InputStream inputStream = zip.getInputStream(serviceEntry);
+    Scanner scanner = new Scanner(inputStream);
+    assertEquals("baz.qux.BazImpl", scanner.next());
+    assertEquals("foo.baz.OtherImpl", scanner.next());
+    assertFalse(scanner.hasNext());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/relocator/RelocatorTest.java b/src/test/java/com/android/tools/r8/relocator/RelocatorTest.java
index 9690ea6..62d8695 100644
--- a/src/test/java/com/android/tools/r8/relocator/RelocatorTest.java
+++ b/src/test/java/com/android/tools/r8/relocator/RelocatorTest.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -75,8 +76,15 @@
     Map<String, String> mapping = new HashMap<>();
     mapping.put(originalPrefix, newPrefix);
     runRelocator(ToolHelper.R8_WITH_DEPS_JAR, mapping, output);
-    inspectAllClassesRelocated(
-        ToolHelper.R8_WITH_DEPS_JAR, output, originalPrefix, newPrefix + ".");
+    // TODO(b/155618698): Extend relocator with a richer language such that java.lang.Object is not
+    //   relocated.
+    CompilationError compilationError =
+        assertThrows(
+            CompilationError.class,
+            () ->
+                inspectAllClassesRelocated(
+                    ToolHelper.R8_WITH_DEPS_JAR, output, originalPrefix, newPrefix + "."));
+    assertThat(compilationError.getMessage(), containsString("must extend class java.lang.Object"));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
index 2d85c6b..17cce81 100644
--- a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
+++ b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
@@ -11,13 +11,12 @@
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
@@ -78,10 +77,9 @@
     DexApplication application =
         new ApplicationReader(originalApplication, options, Timing.empty()).read();
 
-    DexEncodedMethod method = getMethod(originalApplication, methodSig);
+    ProgramMethod method = getProgramMethod(originalApplication, methodSig);
     // Get the IR pre-optimization.
-    IRCode code =
-        method.buildIR(AppView.createForD8(new AppInfo(application), options), Origin.unknown());
+    IRCode code = method.buildIR(AppView.createForD8(new AppInfo(application), options));
 
     // Find the exit block and assert that the value is a phi merging the exceptional edge
     // with the normal edge.
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 36c7766..8ef02dc 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.origin.EmbeddedOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardConfiguration;
@@ -165,6 +166,10 @@
     return getMethodSubject(application, signature).getMethod();
   }
 
+  protected ProgramMethod getProgramMethod(AndroidApp application, MethodSignature signature) {
+    return getMethodSubject(application, signature).getProgramMethod();
+  }
+
   /**
    * Create an application with one method, and processed that application using R8.
    *
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
index e08c9b7..ed7ac4d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
@@ -94,6 +95,11 @@
   }
 
   @Override
+  public ProgramMethod getProgramMethod() {
+    return null;
+  }
+
+  @Override
   public MethodSignature getOriginalSignature() {
     return null;
   }
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 00d00ca..df3f287 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
@@ -35,7 +35,8 @@
   private final DexClass dexClass;
   final ClassNamingForNameMapper naming;
 
-  FoundClassSubject(CodeInspector codeInspector, DexClass dexClass, ClassNamingForNameMapper naming) {
+  FoundClassSubject(
+      CodeInspector codeInspector, DexClass dexClass, ClassNamingForNameMapper naming) {
     this.codeInspector = codeInspector;
     this.dexClass = dexClass;
     this.naming = naming;
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 782bbf0..0e504ca 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
@@ -25,11 +25,11 @@
 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.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.signature.GenericSignatureParser;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.InternalOptions;
@@ -60,13 +60,8 @@
   @Override
   public IRCode buildIR(InternalOptions options) {
     options.programConsumer = DexIndexedConsumer.emptyConsumer();
-    DexEncodedMethod method = getMethod();
-    return method
-        .getCode()
-        .buildIR(
-            method,
-            AppView.createForD8(new AppInfo(codeInspector.application), options),
-            Origin.unknown());
+    return getProgramMethod()
+        .buildIR(AppView.createForD8(new AppInfo(codeInspector.application), options));
   }
 
   @Override
@@ -145,6 +140,11 @@
   }
 
   @Override
+  public ProgramMethod getProgramMethod() {
+    return new ProgramMethod(clazz.getDexProgramClass(), getMethod());
+  }
+
+  @Override
   public MethodSignature getOriginalSignature() {
     MethodSignature signature = getFinalSignature();
     if (clazz.naming == null) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 50911f3..b158404 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.InternalOptions;
@@ -54,6 +55,8 @@
 
   public abstract DexEncodedMethod getMethod();
 
+  public abstract ProgramMethod getProgramMethod();
+
   public Iterator<InstructionSubject> iterateInstructions() {
     return null;
   }
diff --git a/src/test/sampleApks/simple/res/values-da/strings.xml b/src/test/sampleApks/simple/res/values-da/strings.xml
new file mode 100644
index 0000000..26c62ec
--- /dev/null
+++ b/src/test/sampleApks/simple/res/values-da/strings.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+<resources>
+  <string name="app_name">R8 simpel app</string>
+  <string name="referenced_from_layout">Tryk</string>
+  <!-- Do not inlcude referenced_from_code -->
+  <string name="not_used">ikke brugt</string>
+</resources>
diff --git a/tools/build_sample_apk.py b/tools/build_sample_apk.py
index c035520..a08a4ec 100755
--- a/tools/build_sample_apk.py
+++ b/tools/build_sample_apk.py
@@ -20,6 +20,7 @@
 
 
 DEFAULT_AAPT = 'aapt' # Assume in path.
+DEFAULT_AAPT2 = 'aapt2' # Assume in path.
 DEFAULT_D8 = os.path.join(utils.REPO_ROOT, 'tools', 'd8.py')
 DEFAULT_DEXSPLITTER = os.path.join(utils.REPO_ROOT, 'tools', 'dexsplitter.py')
 DEFAULT_JAVAC = jdk.GetJavacExecutable()
@@ -39,6 +40,9 @@
   result.add_option('--aapt',
                     help='aapt executable to use',
                     default=DEFAULT_AAPT)
+  result.add_option('--aapt2',
+                    help='aapt2 executable to use',
+                    default=DEFAULT_AAPT2)
   result.add_option('--api',
                     help='Android api level',
                     default=21,
@@ -50,6 +54,9 @@
   result.add_option('--split',
                     help='Split the app using the split.spec file',
                     default=False, action='store_true')
+  result.add_option('--generate-proto-apk',
+                    help='Use aapt2 to generate the proto version of the apk.',
+                    default=False, action='store_true')
   result.add_option('--install',
                     help='Install the app (including featuresplit)',
                     default=False, action='store_true')
@@ -273,6 +280,19 @@
   if 'adb logcat' in output:
     raise Exception('You have adb logcat running, please close it and rerun')
 
+def generate_proto_apks(apks, options):
+  proto_apks = []
+  for apk in apks:
+    proto_apk = apk + '.proto'
+    cmd = [options.aapt2, 'convert',
+           '-o', proto_apk,
+           '--output-format', 'proto',
+           apk]
+    utils.PrintCmd(cmd)
+    subprocess.check_call(cmd)
+    proto_apks.append(proto_apk)
+  return proto_apks
+
 def Main():
   (options, args) = parse_options()
   apks = []
@@ -287,13 +307,11 @@
   if is_split:
     split(options.app)
     dex_path = get_split_path(options.app, 'base')
-
   temp_apk_path = create_temp_apk(options.app, '')
   aapt_add_dex(options.aapt, dex_path, temp_apk_path)
   apk_path = os.path.join(get_bin_path(options.app), '%s.apk' % options.app)
   apk_utils.sign(temp_apk_path, apk_path,  options.keystore)
   apks.append(apk_path)
-
   if is_split:
     split_temp_apk_path = create_temp_apk(options.app, 'split_')
     aapt_add_dex(options.aapt,
@@ -302,7 +320,9 @@
     split_apk_path = os.path.join(get_bin_path(options.app), 'featuresplit.apk')
     apk_utils.sign(temp_apk_path, split_apk_path,  options.keystore)
     apks.append(split_apk_path)
-
+  if options.generate_proto_apk:
+    proto_apks = generate_proto_apks(apks, options)
+    print('Generated proto apks available at: %s' % ' '.join(proto_apks))
   print('Generated apks available at: %s' % ' '.join(apks))
   if options.install or options.benchmark:
     adb_install(apks)