Merge commit 'e90c98ed1d6bf900c9d699b49f479d23f631ee6b' into dev-release
diff --git a/.gitignore b/.gitignore
index 7a9429b..470dd43 100644
--- a/.gitignore
+++ b/.gitignore
@@ -59,6 +59,8 @@
 third_party/benchmarks/santa-tracker.tar.gz
 third_party/benchmarks/wordpress
 third_party/benchmarks/wordpress.tar.gz
+third_party/binary_compatibility_tests/compiler_api_tests.tar.gz
+third_party/binary_compatibility_tests/compiler_api_tests
 third_party/cf_segments
 third_party/cf_segments.tar.gz
 third_party/chrome/*
diff --git a/build.gradle b/build.gradle
index ce0bc7f..360f21a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -273,8 +273,6 @@
 def r8LibGeneratedKeepRulesPath = "$buildDir/generated/keep.txt"
 def r8LibTestPath = "$buildDir/classes/r8libtest"
 def java11ClassFiles = "$buildDir/classes/java/mainJava11"
-def r8RetracePath = "$buildDir/libs/r8retrace.jar"
-def r8RetraceExludeDepsPath = "$buildDir/libs/r8retrace-exclude-deps.jar"
 
 def osString = OperatingSystem.current().isLinux() ? "linux" :
         OperatingSystem.current().isMacOsX() ? "mac" : "windows"
@@ -303,6 +301,7 @@
                 "android_jar/api-versions",
                 "android_jar/api-database",
                 "api-outlining/simple-app-dump",
+                "binary_compatibility_tests/compiler_api_tests",
                 "core-lambda-stubs",
                 "dart-sdk",
                 "ddmlib",
@@ -1106,30 +1105,6 @@
     outputs.file r8DesugaredPath
 }
 
-task R8Retrace {
-    dependsOn R8Lib
-    dependsOn r8LibCreateTask(
-            "Retrace",
-            ["src/main/keep_retrace.txt"],
-            R8Lib,
-            r8RetracePath,
-    ).dependsOn(R8Lib)
-    outputs.file r8RetracePath
-}
-
-task R8RetraceNoDeps {
-    dependsOn R8LibNoDeps
-    dependsOn r8LibCreateTask(
-            "RetraceNoDeps",
-            ["src/main/keep_retrace.txt"],
-            R8LibNoDeps,
-            r8RetraceExludeDepsPath,
-            "--release",
-            repackageDeps.outputs.files
-    ).dependsOn(R8LibNoDeps)
-    outputs.file r8RetraceExludeDepsPath
-}
-
 task sourceJar(type: Jar, dependsOn: classes) {
     classifier = 'src'
     from sourceSets.main.allSource
@@ -1185,42 +1160,6 @@
     dependsOn buildR8ApiUsageSample
 }
 
-task buildDebugInfoExamplesDex {
-    def examplesDir = file("src/test/java")
-    def hostJar = "debuginfo_examples.jar"
-    def hostDexJar = "debuginfo_examples_dex.jar"
-    task "compile_debuginfo_examples"(type: JavaCompile) {
-        source = fileTree(dir: examplesDir, include: "com/android/tools/r8/debuginfo/*Test.java")
-        destinationDir = file("build/test/debuginfo_examples/classes")
-        classpath = sourceSets.main.compileClasspath
-        sourceCompatibility = JavaVersion.VERSION_1_7
-        targetCompatibility = JavaVersion.VERSION_1_7
-        options.compilerArgs += ["-Xlint:-options"]
-    }
-    task "jar_debuginfo_examples"(type: Jar, dependsOn: "compile_debuginfo_examples") {
-        archiveName = hostJar
-        destinationDir = file("build/test/")
-        from "build/test/debuginfo_examples/classes"
-        include "**/*.class"
-    }
-    task "dex_debuginfo_examples"(type: Exec,
-            dependsOn: ["jar_debuginfo_examples", "downloadDeps"]) {
-        if (OperatingSystem.current().isWindows()) {
-            executable file("tools/windows/dx/bin/dx.bat")
-        } else if (OperatingSystem.current().isMacOsX()) {
-            executable file("tools/mac/dx/bin/dx");
-        } else {
-            executable file("tools/linux/dx/bin/dx");
-        }
-        args "--dex"
-        args "--output=build/test/${hostDexJar}"
-        args "build/test/${hostJar}"
-        inputs.files files("build/test/${hostJar}")
-        outputs.file file("build/test/${hostDexJar}")
-    }
-    dependsOn dex_debuginfo_examples
-}
-
 task buildDebugTestResourcesJars {
     def resourcesDir = file("src/test/debugTestResources")
     def hostJar = "debug_test_resources.jar"
@@ -2379,7 +2318,6 @@
         dependsOn buildSmali
         dependsOn jctfCommonJar
         dependsOn jctfTestsClasses
-        dependsOn buildDebugInfoExamplesDex
         dependsOn buildPreNJdwpTestsJar
         dependsOn buildPreNJdwpTestsDex
         dependsOn compileTestNGRunner
diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg
index 6dd5ceb..11ad90f 100644
--- a/infra/config/global/generated/cr-buildbucket.cfg
+++ b/infra/config/global/generated/cr-buildbucket.cfg
@@ -31,7 +31,6 @@
         cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
         cipd_version: "refs/heads/master"
         properties_j: "builder_group:\"internal.client.r8\""
-        properties_j: "test_options:[\"not_used_but_evaluates_to_python_true\"]"
         properties_j: "test_wrapper:\"tools/archive.py\""
       }
       priority: 25
@@ -57,7 +56,6 @@
         cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
         cipd_version: "refs/heads/master"
         properties_j: "builder_group:\"internal.client.r8\""
-        properties_j: "test_options:[\"not_used_but_evaluates_to_python_true\"]"
         properties_j: "test_wrapper:\"tools/archive.py\""
       }
       priority: 25
@@ -133,7 +131,6 @@
         cipd_package: "infra_internal/recipe_bundles/chrome-internal.googlesource.com/chrome/tools/build_limited/scripts/slave"
         cipd_version: "refs/heads/master"
         properties_j: "builder_group:\"internal.client.r8\""
-        properties_j: "test_options:[\"not_used_but_evaluates_to_python_true\"]"
         properties_j: "test_wrapper:\"tools/archive_desugar_jdk_libs.py\""
       }
       priority: 25
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index e7efbfa..fdf35d6 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -47,7 +47,6 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.function.BiFunction;
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -109,12 +108,10 @@
     private StringConsumer proguardConfigurationConsumer = null;
     private GraphConsumer keptGraphConsumer = null;
     private GraphConsumer mainDexKeptGraphConsumer = null;
-    private BiFunction<String, Long, Boolean> dexClassChecksumFilter = (name, checksum) -> true;
     private final List<FeatureSplit> featureSplits = new ArrayList<>();
     private String synthesizedClassPrefix = "";
     private boolean skipDump = false;
 
-    private boolean allowPartiallyImplementedProguardOptions = false;
     private boolean allowTestProguardOptions =
         System.getProperty("com.android.tools.r8.allowTestProguardOptions") != null;
 
@@ -614,11 +611,6 @@
 
     }
 
-    // Internal for-testing method to add post-processors of the proguard configuration.
-    void allowPartiallyImplementedProguardOptions() {
-      allowPartiallyImplementedProguardOptions = true;
-    }
-
     // Internal for-testing method to allow proguard options only available for testing.
     void allowTestProguardOptions() {
       allowTestProguardOptions = true;
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index d7c008c..7a8a3f6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -191,7 +191,10 @@
       }
       startLine = position.line;
       emittedPosition =
-          new Position(position.line, null, position.getOutermostCaller().method, null);
+          Position.builder()
+              .setLine(position.line)
+              .setMethod(position.getOutermostCaller().method)
+              .build();
     }
     assert emittedPc != pc;
     int previousPc = emittedPc == NO_PC_INFO ? 0 : emittedPc;
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
index 29e68bf..95b2a6f 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -81,6 +81,10 @@
         Constants.ACC_PUBLIC | Constants.ACC_FINAL | Constants.ACC_SYNTHETIC);
   }
 
+  public static FieldAccessFlags createPublicSynthetic() {
+    return fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
+  }
+
   public static FieldAccessFlags fromSharedAccessFlags(int access) {
     assert (access & FLAGS) == access;
     return new FieldAccessFlags(access & FLAGS);
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 296f792..0c78a72 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -276,7 +276,7 @@
       assert enclosingMember == null;
       DexType ownerType = application.getTypeFromName(owner);
       enclosingMember =
-          name == null
+          name == null || name.equals("<clinit>")
               ? new EnclosingMethodAttribute(ownerType)
               : new EnclosingMethodAttribute(application.getMethod(ownerType, name, desc));
     }
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 c554ca5..cd91733 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -997,7 +997,9 @@
     @Override
     public void visitLineNumber(int line, Label start) {
       if (debugParsingOptions.lineInfo) {
-        instructions.add(new CfPosition(getLabel(start), new Position(line, null, method, null)));
+        instructions.add(
+            new CfPosition(
+                getLabel(start), Position.builder().setLine(line).setMethod(method).build()));
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
index 60136ce..4f00ef0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.SuccessfulDataflowAnalysisResult;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.WorkList;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -33,6 +34,11 @@
   // The state of the analysis.
   private final Map<BasicBlock, StateType> blockExitStates = new IdentityHashMap<>();
 
+  // The entry states for each block that satisfies the predicate
+  // shouldCacheBlockEntryStateFor(block). These entry states can be computed from the exit states
+  // of the predecessors, but doing so can be expensive when a block has many predecessors.
+  private final Map<BasicBlock, StateType> blockEntryStatesCache = new IdentityHashMap<>();
+
   public IntraproceduralDataflowAnalysis(
       StateType bottom, AbstractTransferFunction<StateType> transfer) {
     this.bottom = bottom;
@@ -40,20 +46,29 @@
   }
 
   public DataflowAnalysisResult run(BasicBlock root) {
-    return run(WorkList.newIdentityWorkList(root));
+    return run(root, Timing.empty());
   }
 
-  private DataflowAnalysisResult run(WorkList<BasicBlock> worklist) {
+  public DataflowAnalysisResult run(BasicBlock root, Timing timing) {
+    return run(WorkList.newIdentityWorkList(root), timing);
+  }
+
+  private DataflowAnalysisResult run(WorkList<BasicBlock> worklist, Timing timing) {
     while (worklist.hasNext()) {
-      BasicBlock block = worklist.next();
+      BasicBlock initialBlock = worklist.next();
+      BasicBlock block = initialBlock;
       BasicBlock end = null;
       // Compute the abstract state upon entry to the basic block, by joining all the predecessor
       // exit states.
-      StateType state = computeBlockEntryState(block);
+      StateType state =
+          timing.time("Compute block entry state", () -> computeBlockEntryState(initialBlock));
+
+      timing.begin("Compute transfers");
       do {
         for (Instruction instruction : block.getInstructions()) {
           TransferFunctionResult<StateType> transferResult = transfer.apply(instruction, state);
           if (transferResult.isFailedTransferResult()) {
+            timing.end();
             return new FailedDataflowAnalysisResult();
           }
           assert transferResult.isAbstractState();
@@ -66,16 +81,25 @@
           block = null;
         }
       } while (block != null);
+      timing.end();
+
       // Update the block exit state, and re-enqueue all successor blocks if the abstract state
       // changed.
       if (setBlockExitState(end, state)) {
         worklist.addAllIgnoringSeenSet(end.getSuccessors());
       }
+
+      // Add the computed exit state to the entry state of each successor that satisfies the
+      // predicate shouldCacheBlockEntryStateFor(successor).
+      updateBlockEntryStateCacheForSuccessors(end, state);
     }
     return new SuccessfulDataflowAnalysisResult<>(blockExitStates);
   }
 
   private StateType computeBlockEntryState(BasicBlock block) {
+    if (shouldCacheBlockEntryStateFor(block)) {
+      return blockEntryStatesCache.getOrDefault(block, bottom);
+    }
     StateType result = bottom;
     for (BasicBlock predecessor : block.getPredecessors()) {
       StateType edgeState =
@@ -92,4 +116,18 @@
     assert previous == null || state.isGreaterThanOrEquals(previous);
     return !state.equals(previous);
   }
+
+  private void updateBlockEntryStateCacheForSuccessors(BasicBlock block, StateType state) {
+    for (BasicBlock successor : block.getSuccessors()) {
+      if (shouldCacheBlockEntryStateFor(successor)) {
+        StateType edgeState = transfer.computeBlockEntryState(successor, block, state);
+        StateType previous = blockEntryStatesCache.getOrDefault(successor, bottom);
+        blockEntryStatesCache.put(successor, previous.join(edgeState));
+      }
+    }
+  }
+
+  private boolean shouldCacheBlockEntryStateFor(BasicBlock block) {
+    return block.getPredecessors().size() > 2;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
index 06537e3..ad16c77 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
@@ -35,7 +35,12 @@
       preamblePosition =
           methodIsSynthesized
               ? callerPosition
-              : getCanonical(new Position(0, null, method, callerPosition));
+              : getCanonical(
+                  Position.builder()
+                      .setLine(0)
+                      .setMethod(method)
+                      .setCallerPosition(callerPosition)
+                      .build());
     } else {
       this.callerPosition = null;
       isCompilerSynthesizedInlinee = false;
@@ -77,7 +82,7 @@
     return getCanonical(
         caller.isNone()
             ? Position.noneWithMethod(caller.method, callerOfCaller)
-            : new Position(caller.line, caller.file, caller.method, callerOfCaller));
+            : caller.builderWithCopy().setCallerPosition(callerOfCaller).build());
   }
 
   // If we need to emit a synthetic position for exceptional monitor exits, we try to cook up a
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 09a2631..259c18b 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
@@ -46,12 +46,6 @@
         .withNullableItem(p -> p.callerPosition);
   }
 
-  public Position(int line, DexString file, DexMethod method, Position callerPosition) {
-    this(line, file, method, callerPosition, false);
-    assert line >= 0;
-    assert method != null;
-  }
-
   private Position(
       int line, DexString file, DexMethod method, Position callerPosition, boolean synthetic) {
     this.line = line;
@@ -141,15 +135,13 @@
     return callerPosition;
   }
 
-  public Position withCallerPosition(Position callerPosition) {
-    return new Position(line, file, method, callerPosition, synthetic);
-  }
-
   public Position withOutermostCallerPosition(Position newOutermostCallerPosition) {
-    return withCallerPosition(
-        hasCallerPosition()
-            ? getCallerPosition().withOutermostCallerPosition(newOutermostCallerPosition)
-            : newOutermostCallerPosition);
+    return builderWithCopy()
+        .setCallerPosition(
+            hasCallerPosition()
+                ? getCallerPosition().withOutermostCallerPosition(newOutermostCallerPosition)
+                : newOutermostCallerPosition)
+        .build();
   }
 
   @Override
@@ -193,4 +185,57 @@
   public String toString() {
     return toString(false);
   }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public Builder builderWithCopy() {
+    return new Builder()
+        .setLine(line)
+        .setFile(file)
+        .setMethod(method)
+        .setCallerPosition(callerPosition)
+        .setSynthetic(synthetic);
+  }
+
+  public static class Builder {
+
+    public int line;
+    public DexString file;
+    public boolean synthetic;
+    public DexMethod method;
+    public Position callerPosition;
+
+    public Builder setLine(int line) {
+      this.line = line;
+      return this;
+    }
+
+    public Builder setFile(DexString file) {
+      this.file = file;
+      return this;
+    }
+
+    public Builder setSynthetic(boolean synthetic) {
+      this.synthetic = synthetic;
+      return this;
+    }
+
+    public Builder setMethod(DexMethod method) {
+      this.method = method;
+      return this;
+    }
+
+    public Builder setCallerPosition(Position callerPosition) {
+      this.callerPosition = callerPosition;
+      return this;
+    }
+
+    public Position build() {
+      assert line >= 0;
+      assert method != null;
+      return new Position(line, file, method, callerPosition, synthetic);
+    }
+  }
 }
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 83dbd55..5abeb6e 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
@@ -901,10 +901,10 @@
 
   public Position getCanonicalPosition(Position position) {
     return canonicalPositions.getCanonical(
-        new Position(
-            position.line,
-            position.file,
-            position.method,
-            canonicalPositions.canonicalizeCallerPosition(position.callerPosition)));
+        position
+            .builderWithCopy()
+            .setCallerPosition(
+                canonicalPositions.canonicalizeCallerPosition(position.callerPosition))
+            .build());
   }
 }
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 211f77b..4181373 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
@@ -258,11 +258,12 @@
         || entry.callerPosition.getOutermostCaller().method == originalMethod;
 
     return canonicalPositions.getCanonical(
-        new Position(
-            entry.line,
-            entry.sourceFile,
-            entry.method,
-            canonicalPositions.canonicalizeCallerPosition(entry.callerPosition)));
+        Position.builder()
+            .setLine(entry.line)
+            .setFile(entry.sourceFile)
+            .setMethod(entry.method)
+            .setCallerPosition(canonicalPositions.canonicalizeCallerPosition(entry.callerPosition))
+            .build());
   }
 
   @Override
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 4f92d5b..6359283 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
@@ -750,18 +750,25 @@
     outliner.rewriteWithLens();
 
     timing.begin("IR conversion phase 2");
+    timing.begin("Build post method processor");
     PostMethodProcessor postMethodProcessor =
         postMethodProcessorBuilder.build(appView, executorService, timing);
+    timing.end();
     if (postMethodProcessor != null) {
       assert !options.debug;
       assert appView.graphLens() == graphLensForSecondaryOptimizationPass;
+      timing.begin("Process code");
       postMethodProcessor.forEachMethod(
           (method, methodProcessingContext) ->
               processDesugaredMethod(
                   method, feedback, postMethodProcessor, methodProcessingContext),
           feedback,
-          executorService);
+          executorService,
+          timing);
+      timing.end();
+      timing.begin("Update visible optimization info");
       feedback.updateVisibleOptimizationInfo();
+      timing.end();
       assert appView.graphLens() == graphLensForSecondaryOptimizationPass;
     }
     timing.end();
@@ -1143,7 +1150,7 @@
         new MutableMethodConversionOptions(methodProcessor);
     assert holder != null;
 
-    Timing timing = Timing.create(method.qualifiedName(), options);
+    Timing timing = Timing.create(context.toSourceString(), options);
 
     if (Log.ENABLED) {
       Log.debug(getClass(), "Initial (SSA) flow graph for %s:\n%s", method.toSourceString(), code);
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 36c8dc6..a25e9dc 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
@@ -14,20 +14,22 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.PrimaryMethodProcessor.MethodAction;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.logging.Log;
 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.Timing.TimingMerger;
 import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import java.util.ArrayDeque;
+import java.util.Collection;
 import java.util.Deque;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.function.BiConsumer;
 
 public class PostMethodProcessor extends MethodProcessorWithWave {
 
@@ -126,25 +128,35 @@
     return waves;
   }
 
-  void forEachMethod(
-      BiConsumer<ProgramMethod, MethodProcessingContext> consumer,
+  <E extends Exception> void forEachMethod(
+      MethodAction<E> consumer,
       OptimizationFeedbackDelayed feedback,
-      ExecutorService executorService)
+      ExecutorService executorService,
+      Timing timing)
       throws ExecutionException {
+    TimingMerger merger =
+        timing.beginMerger("secondary-processor", ThreadUtils.getNumberOfThreads(executorService));
     while (!waves.isEmpty()) {
       wave = waves.removeFirst();
       assert !wave.isEmpty();
       assert waveExtension.isEmpty();
       do {
         assert feedback.noUpdatesLeft();
-        ThreadUtils.processItems(
-            wave,
-            method -> consumer.accept(method, createMethodProcessingContext(method)),
-            executorService);
+        Collection<Timing> timings =
+            ThreadUtils.processItemsWithResults(
+                wave,
+                method -> {
+                  Timing time = consumer.apply(method, createMethodProcessingContext(method));
+                  time.end();
+                  return time;
+                },
+                executorService);
+        merger.add(timings);
         feedback.updateVisibleOptimizationInfo();
         processed.addAll(wave);
         prepareForWaveExtensionProcessing();
       } while (!wave.isEmpty());
     }
+    merger.end();
   }
 }
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 bbc30e8..cd17565 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
@@ -236,7 +236,10 @@
       fields.add(
           DexEncodedField.syntheticBuilder()
               .setField(getCaptureField(i))
-              .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
+              .setAccessFlags(
+                  appView.options().desugarSpecificOptions().lambdaClassFieldsFinal
+                      ? FieldAccessFlags.createPublicFinalSynthetic()
+                      : FieldAccessFlags.createPublicSynthetic())
               // The api level is computed when tracing.
               .disableAndroidApiLevelCheck()
               .build());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsages.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsages.java
index 84bae0e..d087365 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsages.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/BottomParameterUsages.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.classinliner.analysis;
 
+import com.android.tools.r8.utils.IntObjToObjFunction;
 import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
 
 public class BottomParameterUsages extends ParameterUsages {
@@ -39,6 +40,12 @@
   }
 
   @Override
+  ParameterUsages rebuildParameters(
+      IntObjToObjFunction<ParameterUsagePerContext, ParameterUsagePerContext> transformation) {
+    return this;
+  }
+
+  @Override
   public boolean equals(Object other) {
     return other == INSTANCE;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java
index b8f406a..854188e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java
@@ -12,11 +12,12 @@
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
 import com.android.tools.r8.ir.optimize.classinliner.constraint.ConditionalClassInlinerMethodConstraint;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Timing;
 
 public class ClassInlinerMethodConstraintAnalysis {
 
   public static ClassInlinerMethodConstraint analyze(
-      AppView<AppInfoWithLiveness> appView, ProgramMethod method, IRCode code) {
+      AppView<AppInfoWithLiveness> appView, ProgramMethod method, IRCode code, Timing timing) {
     if (method.getDefinition().isClassInitializer()
         || method.getDefinition().getNumberOfArguments() == 0) {
       return ClassInlinerMethodConstraint.alwaysFalse();
@@ -27,11 +28,13 @@
         new IntraproceduralDataflowAnalysis<>(
             ParameterUsages.bottom(), new TransferFunction(appView, method, code));
     SuccessfulDataflowAnalysisResult<ParameterUsages> result =
-        analysis.run(code.entryBlock()).asSuccessfulAnalysisResult();
+        timing.time(
+            "Data flow analysis",
+            () -> analysis.run(code.entryBlock(), timing).asSuccessfulAnalysisResult());
     if (result == null) {
       return ClassInlinerMethodConstraint.alwaysFalse();
     }
-    ParameterUsages usages = result.join().externalize();
+    ParameterUsages usages = timing.time("Externalize", () -> result.join().externalize());
     if (usages.isBottom()) {
       return ClassInlinerMethodConstraint.alwaysTrue();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsagePerContext.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsagePerContext.java
index 9460e7e..715d88f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsagePerContext.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsagePerContext.java
@@ -9,6 +9,7 @@
 import java.util.Map;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
 
 class NonEmptyParameterUsagePerContext extends ParameterUsagePerContext {
 
@@ -28,6 +29,15 @@
         ImmutableMap.of(DefaultAnalysisContext.getInstance(), ParameterUsage.bottom()));
   }
 
+  public boolean allMatch(BiPredicate<AnalysisContext, ParameterUsage> predicate) {
+    for (Map.Entry<AnalysisContext, ParameterUsage> entry : backing.entrySet()) {
+      if (!predicate.test(entry.getKey(), entry.getValue())) {
+        return false;
+      }
+    }
+    return true;
+  }
+
   void forEach(BiConsumer<AnalysisContext, ParameterUsage> consumer) {
     backing.forEach(consumer);
   }
@@ -85,6 +95,10 @@
     return backing.getOrDefault(context, ParameterUsage.top());
   }
 
+  public int getNumberOfContexts() {
+    return backing.size();
+  }
+
   @Override
   ParameterUsagePerContext rebuild(
       BiFunction<AnalysisContext, ParameterUsage, ParameterUsage> transformation) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsages.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsages.java
index f7b2cae..50fa8a7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsages.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/NonEmptyParameterUsages.java
@@ -176,6 +176,7 @@
                 : usagePerContext);
   }
 
+  @Override
   NonEmptyParameterUsages rebuildParameters(
       IntObjToObjFunction<ParameterUsagePerContext, ParameterUsagePerContext> transformation) {
     Int2ObjectMap<ParameterUsagePerContext> rebuiltBacking = null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsages.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsages.java
index 0cb3a5e..ac81581 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsages.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ParameterUsages.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.classinliner.analysis;
 
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractState;
+import com.android.tools.r8.utils.IntObjToObjFunction;
 
 public abstract class ParameterUsages extends AbstractState<ParameterUsages> {
 
@@ -56,6 +57,9 @@
 
   abstract ParameterUsages put(int parameter, ParameterUsagePerContext usagePerContext);
 
+  abstract ParameterUsages rebuildParameters(
+      IntObjToObjFunction<ParameterUsagePerContext, ParameterUsagePerContext> transformation);
+
   public static BottomParameterUsages bottom() {
     return BottomParameterUsages.getInstance();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
index 822b965..17d5658 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
@@ -93,7 +93,7 @@
       // argument that may be eligible for class inlining.
       if (argument == lastArgument
           && result.asNonEmpty().allMatch((context, usagePerContext) -> usagePerContext.isTop())) {
-        return new FailedTransferFunctionResult<>();
+        return fail();
       }
       return result;
     }
@@ -104,7 +104,8 @@
     }
     assert !state.isBottom();
     assert !state.isTop();
-    return apply(instruction, state.asNonEmpty());
+    ParameterUsages outState = apply(instruction, state.asNonEmpty());
+    return outState != state ? widen(outState) : outState;
   }
 
   private ParameterUsages apply(Instruction instruction, NonEmptyParameterUsages state) {
@@ -338,6 +339,10 @@
         theReturn.returnValue(), (context, usage) -> usage.setParameterReturned());
   }
 
+  private FailedTransferFunctionResult<ParameterUsages> fail() {
+    return new FailedTransferFunctionResult<>();
+  }
+
   private ParameterUsages fail(Instruction instruction, NonEmptyParameterUsages state) {
     return state.abandonClassInliningInCurrentContexts(
         instruction.inValues(), this::isArgumentOfInterest);
@@ -383,4 +388,29 @@
     // We can only class inline a parameter that is either java.lang.Object or an interface type.
     return clazz.getType() == dexItemFactory.objectType || clazz.isInterface();
   }
+
+  private TransferFunctionResult<ParameterUsages> widen(ParameterUsages state) {
+    // Currently we only fork one context.
+    int maxNumberOfContexts = 1;
+    ParameterUsages widened =
+        state.rebuildParameters(
+            (parameter, usagePerContext) -> {
+              if (usagePerContext.isBottom() || usagePerContext.isTop()) {
+                return usagePerContext;
+              }
+              NonEmptyParameterUsagePerContext nonEmptyUsagePerContext = usagePerContext.asKnown();
+              if (nonEmptyUsagePerContext.getNumberOfContexts() == maxNumberOfContexts
+                  && nonEmptyUsagePerContext.allMatch(
+                      (context, usageInContext) -> usageInContext.isTop())) {
+                return ParameterUsagePerContext.top();
+              }
+              return usagePerContext;
+            });
+    if (!widened.isBottom()
+        && !widened.isTop()
+        && widened.asNonEmpty().allMatch((parameter, usagePerContext) -> usagePerContext.isTop())) {
+      return fail();
+    }
+    return widened;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsages.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsages.java
index 13e32a7..f6725a9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsages.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/UnknownParameterUsages.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.classinliner.analysis;
 
+import com.android.tools.r8.utils.IntObjToObjFunction;
+
 public class UnknownParameterUsages extends ParameterUsages {
 
   private static final UnknownParameterUsages INSTANCE = new UnknownParameterUsages();
@@ -35,6 +37,12 @@
   }
 
   @Override
+  ParameterUsages rebuildParameters(
+      IntObjToObjFunction<ParameterUsagePerContext, ParameterUsagePerContext> transformation) {
+    return this;
+  }
+
+  @Override
   public boolean equals(Object other) {
     return other == INSTANCE;
   }
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 9836791..e28b6ad 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
@@ -783,15 +783,10 @@
       OptimizationFeedback feedback,
       Timing timing) {
     timing.begin("Compute class inlining constraint");
-    computeClassInlinerMethodConstraint(method, code, feedback);
-    timing.end();
-  }
-
-  private void computeClassInlinerMethodConstraint(
-      ProgramMethod method, IRCode code, OptimizationFeedback feedback) {
     ClassInlinerMethodConstraint classInlinerMethodConstraint =
-        ClassInlinerMethodConstraintAnalysis.analyze(appView, method, code);
+        ClassInlinerMethodConstraintAnalysis.analyze(appView, method, code, timing);
     feedback.setClassInlinerMethodConstraint(method, classInlinerMethodConstraint);
+    timing.end();
   }
 
   private void computeEnumUnboxerMethodClassification(
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 00028ff..373f584 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind;
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
+import com.android.tools.r8.naming.mappinginformation.OutlineCallsiteMappingInformation;
 import com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation;
 import com.android.tools.r8.utils.ChainableStringConsumer;
 import com.android.tools.r8.utils.ThrowingConsumer;
@@ -23,6 +24,8 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
 
 /**
  * Stores name information for a class.
@@ -428,7 +431,6 @@
 
     private MappedRange(
         Range minifiedRange, MethodSignature signature, Range originalRange, String renamedName) {
-
       this.minifiedRange = minifiedRange;
       this.signature = signature;
       this.originalRange = originalRange;
@@ -448,23 +450,45 @@
 
     public boolean isCompilerSynthesized() {
       for (MappingInformation info : additionalMappingInfo) {
-        if (info.isCompilerSynthesizedMappingInformation()) {
+        if (info.isCompilerSynthesizedMappingInformation() || info.isOutlineMappingInformation()) {
           return true;
         }
       }
       return false;
     }
 
-    public List<RewriteFrameMappingInformation> getRewriteFrameMappingInformation() {
-      ImmutableList.Builder<RewriteFrameMappingInformation> builder = ImmutableList.builder();
+    public boolean isOutlineFrame() {
+      for (MappingInformation info : additionalMappingInfo) {
+        if (info.isOutlineMappingInformation()) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    public <T> List<T> filter(
+        Predicate<MappingInformation> predicate, Function<MappingInformation, T> mapper) {
+      ImmutableList.Builder<T> builder = ImmutableList.builder();
       for (MappingInformation mappingInformation : additionalMappingInfo) {
-        if (mappingInformation.isRewriteFrameMappingInformation()) {
-          builder.add(mappingInformation.asRewriteFrameMappingInformation());
+        if (predicate.test(mappingInformation)) {
+          builder.add(mapper.apply(mappingInformation));
         }
       }
       return builder.build();
     }
 
+    public List<OutlineCallsiteMappingInformation> getOutlineCallsiteInformation() {
+      return filter(
+          MappingInformation::isOutlineCallsiteInformation,
+          MappingInformation::asOutlineCallsiteInformation);
+    }
+
+    public List<RewriteFrameMappingInformation> getRewriteFrameMappingInformation() {
+      return filter(
+          MappingInformation::isRewriteFrameMappingInformation,
+          MappingInformation::asRewriteFrameMappingInformation);
+    }
+
     public int getOriginalLineNumber(int lineNumberAfterMinification) {
       if (minifiedRange == null) {
         // General mapping without concrete line numbers: "a() -> b"
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
index 7e7cff8..85e7b1a 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
@@ -59,7 +59,6 @@
 
   private ProguardMapSupplier(ClassNameMapper classNameMapper, InternalOptions options) {
     assert classNameMapper != null;
-    assert !classNameMapper.isEmpty();
     this.classNameMapper = classNameMapper.sorted();
     this.consumer =
         InternalOptions.assertionsEnabled()
@@ -71,7 +70,7 @@
 
   public static ProguardMapSupplier create(
       ClassNameMapper classNameMapper, InternalOptions options) {
-    return classNameMapper.isEmpty() ? null : new ProguardMapSupplier(classNameMapper, options);
+    return new ProguardMapSupplier(classNameMapper, options);
   }
 
   public ProguardMapId writeProguardMap() {
diff --git a/src/main/java/com/android/tools/r8/naming/Range.java b/src/main/java/com/android/tools/r8/naming/Range.java
index a8d8563..8624aad 100644
--- a/src/main/java/com/android/tools/r8/naming/Range.java
+++ b/src/main/java/com/android/tools/r8/naming/Range.java
@@ -49,6 +49,13 @@
     return from == range.from && to == range.to && isCardinal == range.isCardinal;
   }
 
+  public int span() {
+    if (isCardinal) {
+      return 1;
+    }
+    return (to - from) + 1;
+  }
+
   @Override
   public int hashCode() {
     return Objects.hash(from, to, isCardinal);
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
index 9232500..00a411f 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
@@ -34,10 +34,18 @@
     return false;
   }
 
+  public boolean isOutlineCallsiteInformation() {
+    return false;
+  }
+
   public boolean isCompilerSynthesizedMappingInformation() {
     return false;
   }
 
+  public boolean isOutlineMappingInformation() {
+    return false;
+  }
+
   public MapVersionMappingInformation asMapVersionMappingInformation() {
     return null;
   }
@@ -58,6 +66,10 @@
     return null;
   }
 
+  public OutlineCallsiteMappingInformation asOutlineCallsiteInformation() {
+    return null;
+  }
+
   public abstract boolean allowOther(MappingInformation information);
 
   public static void fromJsonObject(
@@ -113,6 +125,12 @@
       case RewriteFrameMappingInformation.ID:
         RewriteFrameMappingInformation.deserialize(version, object, onMappingInfo);
         return;
+      case OutlineMappingInformation.ID:
+        OutlineMappingInformation.deserialize(version, onMappingInfo);
+        return;
+      case OutlineCallsiteMappingInformation.ID:
+        OutlineCallsiteMappingInformation.deserialize(version, object, onMappingInfo);
+        return;
       default:
         diagnosticsHandler.info(MappingInformationDiagnostics.noHandlerFor(lineNumber, id));
         UnknownJsonMappingInformation.deserialize(id, object, onMappingInfo);
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineCallsiteMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineCallsiteMappingInformation.java
new file mode 100644
index 0000000..d451d82
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineCallsiteMappingInformation.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.mappinginformation;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.naming.MapVersion;
+import com.google.gson.JsonObject;
+import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2IntSortedMap;
+import java.util.function.Consumer;
+
+public class OutlineCallsiteMappingInformation extends MappingInformation {
+
+  public static final MapVersion SUPPORTED_VERSION = MapVersion.MAP_VERSION_EXPERIMENTAL;
+  public static final String ID = "com.android.tools.r8.outlineCallsite";
+
+  private static final String POSITIONS_KEY = "positions";
+
+  private final Int2IntSortedMap positions;
+
+  private OutlineCallsiteMappingInformation(Int2IntSortedMap positions) {
+    this.positions = positions;
+  }
+
+  @Override
+  public String getId() {
+    return ID;
+  }
+
+  @Override
+  public String serialize() {
+    throw new CompilationError("Should not yet serialize this");
+  }
+
+  @Override
+  public boolean allowOther(MappingInformation information) {
+    return !information.isOutlineCallsiteInformation();
+  }
+
+  @Override
+  public boolean isOutlineCallsiteInformation() {
+    return true;
+  }
+
+  @Override
+  public OutlineCallsiteMappingInformation asOutlineCallsiteInformation() {
+    return this;
+  }
+
+  public int rewritePosition(int originalPosition) {
+    return positions.getOrDefault(originalPosition, originalPosition);
+  }
+
+  public static OutlineCallsiteMappingInformation create(Int2IntSortedMap positions) {
+    return new OutlineCallsiteMappingInformation(positions);
+  }
+
+  public static boolean isSupported(MapVersion version) {
+    return version.isGreaterThanOrEqualTo(SUPPORTED_VERSION);
+  }
+
+  public static void deserialize(
+      MapVersion version, JsonObject object, Consumer<MappingInformation> onMappingInfo) {
+    if (isSupported(version)) {
+      JsonObject postionsMapObject = object.getAsJsonObject(POSITIONS_KEY);
+      if (postionsMapObject == null) {
+        throw new CompilationError(
+            "Expected '" + POSITIONS_KEY + "' to be present: " + object.getAsString());
+      }
+      Int2IntSortedMap positionsMap = new Int2IntLinkedOpenHashMap();
+      postionsMapObject
+          .entrySet()
+          .forEach(
+              entry -> {
+                try {
+                  String key = entry.getKey();
+                  int originalPosition = Integer.parseInt(key);
+                  int newPosition = entry.getValue().getAsInt();
+                  positionsMap.put(originalPosition, newPosition);
+                } catch (Throwable ex) {
+                  throw new CompilationError("Invalid position entry: " + entry.toString());
+                }
+              });
+      onMappingInfo.accept(OutlineCallsiteMappingInformation.create(positionsMap));
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineMappingInformation.java
new file mode 100644
index 0000000..697c5b6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/OutlineMappingInformation.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.mappinginformation;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.naming.MapVersion;
+import java.util.function.Consumer;
+
+public class OutlineMappingInformation extends MappingInformation {
+
+  public static final MapVersion SUPPORTED_VERSION = MapVersion.MAP_VERSION_EXPERIMENTAL;
+  public static final String ID = "com.android.tools.r8.outline";
+
+  @Override
+  public String getId() {
+    return ID;
+  }
+
+  @Override
+  public String serialize() {
+    throw new CompilationError("Should not yet serialize this");
+  }
+
+  @Override
+  public boolean allowOther(MappingInformation information) {
+    return true;
+  }
+
+  @Override
+  public boolean isOutlineMappingInformation() {
+    return true;
+  }
+
+  public static boolean isSupported(MapVersion version) {
+    return version.isGreaterThanOrEqualTo(SUPPORTED_VERSION);
+  }
+
+  public static void deserialize(MapVersion version, Consumer<MappingInformation> onMappingInfo) {
+    if (isSupported(version)) {
+      onMappingInfo.accept(new OutlineMappingInformation());
+    }
+  }
+
+  public static OutlineMappingInformation.Builder builder() {
+    return new OutlineMappingInformation.Builder();
+  }
+
+  public static class Builder {
+
+    public OutlineMappingInformation build() {
+      return new OutlineMappingInformation();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/RewriteFrameMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/RewriteFrameMappingInformation.java
index cc8c795..d9d8c4b 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/RewriteFrameMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/RewriteFrameMappingInformation.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.naming.mappinginformation;
 
+import static com.android.tools.r8.naming.mappinginformation.RewriteFrameMappingInformation.RemoveInnerFramesAction.REMOVE_INNER_FRAMES_NAME;
+
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.naming.MapVersion;
@@ -15,6 +17,7 @@
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonPrimitive;
+import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -99,6 +102,11 @@
     return this;
   }
 
+  public static RewriteFrameMappingInformation create(
+      List<Condition> conditions, List<RewriteAction> actions) {
+    return new RewriteFrameMappingInformation(conditions, actions);
+  }
+
   public abstract static class Condition {
 
     protected abstract JsonPrimitive serialize();
@@ -114,7 +122,7 @@
       if (ThrowsCondition.FUNCTION_NAME.equals(functionName)) {
         return ThrowsCondition.deserialize(contents);
       }
-      throw new Unimplemented("Unexpected condition: " + elementString);
+      throw new CompilationError("Unexpected condition: " + elementString);
     }
 
     public boolean isThrowsCondition() {
@@ -173,21 +181,7 @@
 
   public abstract static class RewriteAction {
 
-    static final String REMOVE_INNER_FRAMES_SERIALIZED_NAME = "removeInnerFrames";
-
-    private static final String FUNCTION_KEY = "function";
-    private static final String ARGUMENTS_KEY = "arguments";
-
-    abstract String serializeName();
-
-    abstract JsonArray getArguments();
-
-    JsonElement serialize() {
-      JsonObject jsonObject = new JsonObject();
-      jsonObject.add(FUNCTION_KEY, new JsonPrimitive(serializeName()));
-      jsonObject.add(ARGUMENTS_KEY, getArguments());
-      return jsonObject;
-    }
+    abstract JsonElement serialize();
 
     private static RewriteAction deserialize(JsonElement element) {
       String functionString = element.getAsString();
@@ -198,8 +192,8 @@
       }
       String functionName = functionString.substring(0, startArgsIndex);
       String args = functionString.substring(startArgsIndex + 1, endArgsIndex);
-      if (REMOVE_INNER_FRAMES_SERIALIZED_NAME.equals(functionName)) {
-        return RemoveInnerFramesAction.create(args);
+      if (REMOVE_INNER_FRAMES_NAME.equals(functionName)) {
+        return RemoveInnerFramesAction.deserialize(args);
       }
       assert false : "Unknown function " + functionName;
       throw new Unimplemented("Unexpected action: " + functionName);
@@ -218,6 +212,8 @@
 
   public static class RemoveInnerFramesAction extends RewriteAction {
 
+    static final String REMOVE_INNER_FRAMES_NAME = "removeInnerFrames";
+
     private final int numberOfFrames;
 
     public RemoveInnerFramesAction(int numberOfFrames) {
@@ -229,19 +225,8 @@
     }
 
     @Override
-    String serializeName() {
-      return REMOVE_INNER_FRAMES_SERIALIZED_NAME;
-    }
-
-    @Override
-    JsonArray getArguments() {
-      JsonArray arguments = new JsonArray();
-      arguments.add(numberOfFrames);
-      return arguments;
-    }
-
-    static RemoveInnerFramesAction create(String args) {
-      return new RemoveInnerFramesAction(Integer.parseInt(args));
+    JsonElement serialize() {
+      return new JsonPrimitive(REMOVE_INNER_FRAMES_NAME + "(" + numberOfFrames + ")");
     }
 
     @Override
@@ -258,5 +243,41 @@
     public void evaluate(RetraceStackTraceCurrentEvaluationInformation.Builder builder) {
       builder.incrementRemoveInnerFramesCount(numberOfFrames);
     }
+
+    static RemoveInnerFramesAction create(int numberOfFrames) {
+      return new RemoveInnerFramesAction(numberOfFrames);
+    }
+
+    public static RemoveInnerFramesAction deserialize(String contents) {
+      try {
+        return create(Integer.parseInt(contents));
+      } catch (NumberFormatException nfe) {
+        throw new CompilationError(
+            "Unexpected number for " + REMOVE_INNER_FRAMES_NAME + ": " + contents);
+      }
+    }
+  }
+
+  public static class RewritePreviousObfuscatedPosition extends RewriteAction {
+
+    private final Int2IntMap rewriteMap;
+
+    private RewritePreviousObfuscatedPosition(Int2IntMap rewriteMap) {
+      this.rewriteMap = rewriteMap;
+    }
+
+    @Override
+    JsonElement serialize() {
+      throw new CompilationError("Do not serialize this");
+    }
+
+    @Override
+    public void evaluate(RetraceStackTraceCurrentEvaluationInformation.Builder builder) {
+      builder.setPosition(rewriteMap.getOrDefault(builder.getPosition(), builder.getPosition()));
+    }
+
+    public static RewritePreviousObfuscatedPosition create(Int2IntMap rewriteMap) {
+      return new RewritePreviousObfuscatedPosition(rewriteMap);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index c474a8a..5bb9ca1 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -199,8 +199,9 @@
               ST parsedLine = stackTraceLineParser.parse(stackTraceLine);
               List<RetracedNodeState<T, ST>> newLeaves = new ArrayList<>();
               for (RetracedNodeState<T, ST> previousNode : acc) {
-                proxyRetracer
-                    .retrace(parsedLine, previousNode.context)
+                RetraceStackTraceElementProxyResult<T, ST> result =
+                    proxyRetracer.retrace(parsedLine, previousNode.context);
+                result.stream()
                     .forEach(
                         retracedElement -> {
                           if (retracedElement.isTopFrame() || !retracedElement.hasRetracedClass()) {
@@ -212,7 +213,7 @@
                 if (!previousNode.hasChildren()) {
                   // This happens when there is nothing to retrace. Add the node to newLeaves to
                   // ensure we keep retracing this path.
-                  previousNode.addChild(null, RetraceStackTraceContext.empty());
+                  previousNode.addChild(null, result.getResultContext());
                 }
                 newLeaves.addAll(previousNode.getChildren());
               }
@@ -232,8 +233,7 @@
     Map<RetraceStackTraceElementProxy<T, ST>, List<T>> ambiguousBlocks = new HashMap<>();
     List<RetraceStackTraceElementProxy<T, ST>> ambiguousKeys = new ArrayList<>();
     ST parsedLine = stackTraceLineParser.parse(stackTraceFrame);
-    proxyRetracer
-        .retrace(parsedLine, RetraceStackTraceContext.empty())
+    proxyRetracer.retrace(parsedLine, RetraceStackTraceContext.empty()).stream()
         .forEach(
             retracedElement -> {
               if (retracedElement.isTopFrame() || !retracedElement.hasRetracedClass()) {
@@ -259,8 +259,7 @@
    */
   public List<T> retraceLine(T stackTraceLine) {
     ST parsedLine = stackTraceLineParser.parse(stackTraceLine);
-    return proxyRetracer
-        .retrace(parsedLine, RetraceStackTraceContext.empty())
+    return proxyRetracer.retrace(parsedLine, RetraceStackTraceContext.empty()).stream()
         .map(
             retraceFrame -> {
               retraceFrame.getOriginalItem().toRetracedItem(retraceFrame, isVerbose);
@@ -483,15 +482,20 @@
               one,
               other,
               RetraceStackTraceElementProxy::hasSourceFile,
-              RetraceStackTraceElementProxy::getSourceFile)
-          // TODO(b/201042571): This will have to change.
-          || testNotEqualProperty(
-              one,
-              other,
-              RetraceStackTraceElementProxy::hasLineNumber,
-              RetraceStackTraceElementProxy::getLineNumber)) {
+              RetraceStackTraceElementProxy::getSourceFile)) {
         return false;
       }
+      assert one.getOriginalItem() == other.getOriginalItem();
+      if (isVerbose
+          || (one.getOriginalItem().hasLineNumber() && one.getOriginalItem().getLineNumber() > 0)) {
+        if (testNotEqualProperty(
+            one,
+            other,
+            RetraceStackTraceElementProxy::hasLineNumber,
+            RetraceStackTraceElementProxy::getLineNumber)) {
+          return false;
+        }
+      }
       if (one.hasRetracedMethod() != other.hasRetracedMethod()) {
         return false;
       }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
index 9f5bc71..c2bd31a 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
@@ -12,8 +12,6 @@
 @Keep
 public interface RetraceClassResult extends RetraceResult<RetraceClassElement> {
 
-  boolean hasRetraceResult();
-
   RetraceFieldResult lookupField(String fieldName);
 
   RetraceFieldResult lookupField(String fieldName, TypeReference fieldType);
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceFrameElement.java b/src/main/java/com/android/tools/r8/retrace/RetraceFrameElement.java
index 419f6b3..ff7d330 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceFrameElement.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceFrameElement.java
@@ -29,5 +29,5 @@
 
   List<? extends RetracedMethodReference> getOuterFrames();
 
-  RetraceStackTraceContext getContext();
+  RetraceStackTraceContext getRetraceStackTraceContext();
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
index 805443c..f06fad3 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
@@ -5,9 +5,10 @@
 package com.android.tools.r8.retrace;
 
 import com.android.tools.r8.Keep;
+import java.util.OptionalInt;
 
 @Keep
 public interface RetraceMethodResult extends RetraceResult<RetraceMethodElement> {
 
-  RetraceFrameResult narrowByPosition(RetraceStackTraceContext context, int position);
+  RetraceFrameResult narrowByPosition(RetraceStackTraceContext context, OptionalInt position);
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceResult.java
index 1bc6ed6..7ff9c4e 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceResult.java
@@ -31,4 +31,6 @@
   default void forEach(Consumer<E> action) {
     stream().forEach(action);
   }
+
+  boolean isEmpty();
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceElementProxyResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceElementProxyResult.java
new file mode 100644
index 0000000..1db1f08
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceElementProxyResult.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace;
+
+import com.android.tools.r8.Keep;
+import java.util.stream.Stream;
+
+@Keep
+public interface RetraceStackTraceElementProxyResult<T, ST extends StackTraceElementProxy<T, ST>> {
+
+  Stream<? extends RetraceStackTraceElementProxy<T, ST>> stream();
+
+  /**
+   * If the stream is empty, use getResultContext to obtain the resulting stack trace context. Due
+   * to the lazyness of streams the result is only populated after querying the stream.
+   *
+   * @return the resulting stack trace context.
+   */
+  RetraceStackTraceContext getResultContext();
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/Retracer.java b/src/main/java/com/android/tools/r8/retrace/Retracer.java
index 34d413b..293ac8e 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retracer.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.retrace.internal.RetracerImpl;
+import java.util.OptionalInt;
 
 /** This is the main api interface for retrace. */
 @Keep
@@ -20,10 +21,10 @@
 
   RetraceMethodResult retraceMethod(MethodReference methodReference);
 
-  RetraceFrameResult retraceFrame(MethodReference methodReference, int position);
+  RetraceFrameResult retraceFrame(MethodReference methodReference, OptionalInt position);
 
   RetraceFrameResult retraceFrame(
-      MethodReference methodReference, int position, RetraceStackTraceContext context);
+      MethodReference methodReference, OptionalInt position, RetraceStackTraceContext context);
 
   RetraceFieldResult retraceField(FieldReference fieldReference);
 
diff --git a/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java
index 851bbc2..e792ef0 100644
--- a/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/StackTraceElementProxyRetracer.java
@@ -6,13 +6,11 @@
 
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.retrace.internal.StackTraceElementProxyRetracerImpl;
-import java.util.stream.Stream;
 
 @Keep
 public interface StackTraceElementProxyRetracer<T, ST extends StackTraceElementProxy<T, ST>> {
 
-  Stream<? extends RetraceStackTraceElementProxy<T, ST>> retrace(
-      ST element, RetraceStackTraceContext context);
+  RetraceStackTraceElementProxyResult<T, ST> retrace(ST element, RetraceStackTraceContext context);
 
   static <T, ST extends StackTraceElementProxy<T, ST>>
       StackTraceElementProxyRetracer<T, ST> createDefault(Retracer retracer) {
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
index f94035b..0d834a3 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
@@ -6,7 +6,6 @@
 
 
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
-import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
@@ -20,7 +19,6 @@
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.RetraceUnknownJsonMappingInformationResult;
 import com.android.tools.r8.retrace.RetracedSourceFile;
-import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
@@ -138,64 +136,19 @@
             Reference.method(obfuscatedReference, methodName, formalTypes, returnType)));
   }
 
+  private RetraceFrameResultImpl lookupFrame(
+      RetraceStackTraceContext context, OptionalInt position, MethodDefinition definition) {
+    return lookupMethod(definition).narrowByPosition(context, position);
+  }
+
   @Override
   public RetraceThrownExceptionResultImpl lookupThrownException(RetraceStackTraceContext context) {
     return new RetraceThrownExceptionResultImpl(
         (RetraceStackTraceContextImpl) context, obfuscatedReference, mapper);
   }
 
-  private RetraceFrameResultImpl lookupFrame(
-      RetraceStackTraceContext context, OptionalInt position, MethodDefinition definition) {
-    List<Pair<RetraceClassElementImpl, List<MappedRange>>> mappings = new ArrayList<>();
-    internalStream()
-        .forEach(
-            element -> {
-              getMappedRangesForFrame(element, definition, position)
-                  .forEach(
-                      mappedRanges -> {
-                        mappings.add(new Pair<>(element, mappedRanges));
-                      });
-            });
-    return new RetraceFrameResultImpl(
-        this, mappings, definition, position, retracer, (RetraceStackTraceContextImpl) context);
-  }
-
-  private List<List<MappedRange>> getMappedRangesForFrame(
-      RetraceClassElementImpl element, MethodDefinition definition, OptionalInt position) {
-    List<List<MappedRange>> overloadedRanges = new ArrayList<>();
-    if (mapper == null) {
-      overloadedRanges.add(null);
-      return overloadedRanges;
-    }
-    assert element.mapper != null;
-    MappedRangesOfName mappedRanges = mapper.mappedRangesByRenamedName.get(definition.getName());
-    if (mappedRanges == null || mappedRanges.getMappedRanges().isEmpty()) {
-      overloadedRanges.add(null);
-      return overloadedRanges;
-    }
-    List<MappedRange> mappedRangesForPosition = null;
-    if (position.isPresent() && position.getAsInt() >= 0) {
-      mappedRangesForPosition = mappedRanges.allRangesForLine(position.getAsInt(), false);
-    }
-    if (mappedRangesForPosition == null || mappedRangesForPosition.isEmpty()) {
-      mappedRangesForPosition = mappedRanges.getMappedRanges();
-    }
-    assert mappedRangesForPosition != null && !mappedRangesForPosition.isEmpty();
-    // Mapped ranges can have references to overloaded signatures. We distinguish those by looking
-    // at the cardinal mapping range.
-    for (MappedRange mappedRange : mappedRangesForPosition) {
-      if (overloadedRanges.isEmpty()
-          || mappedRange.originalRange == null
-          || !mappedRange.originalRange.isCardinal) {
-        overloadedRanges.add(new ArrayList<>());
-      }
-      ListUtils.last(overloadedRanges).add(mappedRange);
-    }
-    return overloadedRanges;
-  }
-
   @Override
-  public boolean hasRetraceResult() {
+  public boolean isEmpty() {
     return mapper != null;
   }
 
@@ -366,23 +319,7 @@
 
     private RetraceFrameResultImpl lookupFrame(
         RetraceStackTraceContext context, OptionalInt position, MethodDefinition definition) {
-      MethodDefinition methodDefinition =
-          MethodDefinition.create(classReference.getClassReference(), definition.getName());
-      ImmutableList.Builder<Pair<RetraceClassElementImpl, List<MappedRange>>> builder =
-          ImmutableList.builder();
-      classResult
-          .getMappedRangesForFrame(this, methodDefinition, position)
-          .forEach(
-              mappedRanges -> {
-                builder.add(new Pair<>(this, mappedRanges));
-              });
-      return new RetraceFrameResultImpl(
-          classResult,
-          builder.build(),
-          methodDefinition,
-          position,
-          classResult.retracer,
-          (RetraceStackTraceContextImpl) context);
+      return classResult.lookupFrame(context, position, definition);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java
index 38667c4..33abecc 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java
@@ -92,6 +92,11 @@
     return mappings.size() > 1;
   }
 
+  @Override
+  public boolean isEmpty() {
+    return memberNamings == null || memberNamings.isEmpty();
+  }
+
   public static class ElementImpl implements RetraceFieldElement {
 
     private final RetracedFieldReferenceImpl fieldReference;
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
index a2936bf..ee295e2 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.retrace.internal.RetraceClassResultImpl.RetraceClassElementImpl;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.OptionalUtils;
 import com.android.tools.r8.utils.Pair;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
@@ -65,11 +66,15 @@
       }
       List<MappedRange> methodRanges = mappedRanges.get(0).getSecond();
       if (methodRanges != null && !methodRanges.isEmpty()) {
-        MappedRange lastRange = methodRanges.get(0);
+        MappedRange initialRange = methodRanges.get(0);
         for (MappedRange mappedRange : methodRanges) {
-          if (mappedRange != lastRange
+          if (isMappedRangeAmbiguous(mappedRange)) {
+            isAmbiguousCache = OptionalBool.TRUE;
+            return true;
+          }
+          if (mappedRange != initialRange
               && (mappedRange.minifiedRange == null
-                  || !mappedRange.minifiedRange.equals(lastRange.minifiedRange))) {
+                  || !mappedRange.minifiedRange.equals(initialRange.minifiedRange))) {
             isAmbiguousCache = OptionalBool.TRUE;
             return true;
           }
@@ -81,6 +86,16 @@
     return isAmbiguousCache.isTrue();
   }
 
+  private boolean isMappedRangeAmbiguous(MappedRange mappedRange) {
+    if (mappedRange.originalRange == null || mappedRange.originalRange.span() == 1) {
+      // If there is no original position or all maps to a single position, the result is not
+      // ambiguous.
+      return false;
+    }
+    return mappedRange.minifiedRange == null
+        || mappedRange.minifiedRange.span() != mappedRange.originalRange.span();
+  }
+
   @Override
   public Stream<RetraceFrameElement> stream() {
     return mappedRanges.stream()
@@ -98,8 +113,7 @@
                                 classElement.getRetracedClass().getClassReference())),
                         ImmutableList.of(),
                         obfuscatedPosition,
-                        retracer,
-                        context));
+                        retracer));
               }
               // Iterate over mapped ranges that may have different positions than specified.
               List<ElementImpl> ambiguousFrames = new ArrayList<>();
@@ -109,50 +123,105 @@
                 MappedRange mappedRange = mappedRanges.get(i);
                 if (minifiedRange == null || !minifiedRange.equals(mappedRange.minifiedRange)) {
                   // This is a new frame
-                  ambiguousFrames.add(
-                      elementFromMappedRanges(mappedRangesForElement, classElement));
+                  separateAmbiguousOriginalPositions(
+                      classElement, mappedRangesForElement, ambiguousFrames);
                   mappedRangesForElement = new ArrayList<>();
+                  minifiedRange = mappedRange.minifiedRange;
                 }
                 mappedRangesForElement.add(mappedRange);
               }
-              ambiguousFrames.add(elementFromMappedRanges(mappedRangesForElement, classElement));
+              separateAmbiguousOriginalPositions(
+                  classElement, mappedRangesForElement, ambiguousFrames);
               return ambiguousFrames.stream();
             });
   }
 
+  private void separateAmbiguousOriginalPositions(
+      RetraceClassElementImpl classElement,
+      List<MappedRange> frames,
+      List<ElementImpl> allAmbiguousElements) {
+    // We have a single list of frames where minified positional information may produce ambiguous
+    // results.
+    if (!isAmbiguous() || !isMappedRangeAmbiguous(frames.get(0))) {
+      allAmbiguousElements.add(
+          elementFromMappedRanges(
+              ListUtils.map(frames, MappedRangeForFrame::create), classElement));
+      return;
+    }
+    assert frames.size() > 0;
+    assert frames.get(0).originalRange != null
+        && frames.get(0).originalRange.to > frames.get(0).originalRange.from;
+    List<List<MappedRangeForFrame>> newFrames = new ArrayList<>();
+    ListUtils.forEachWithIndex(
+        frames,
+        (frame, index) -> {
+          // Only the top inline can give rise to ambiguity since the remaining inline frames will
+          // have a single line number.
+          if (index == 0) {
+            for (int i = frame.originalRange.from; i <= frame.originalRange.to; i++) {
+              List<MappedRangeForFrame> ambiguousFrames = new ArrayList<>();
+              ambiguousFrames.add(MappedRangeForFrame.create(frame, OptionalInt.of(i)));
+              newFrames.add(ambiguousFrames);
+            }
+          } else {
+            newFrames.forEach(
+                ambiguousFrames -> {
+                  ambiguousFrames.add(MappedRangeForFrame.create(frame));
+                });
+          }
+        });
+    newFrames.forEach(
+        ambiguousFrames -> {
+          allAmbiguousElements.add(elementFromMappedRanges(ambiguousFrames, classElement));
+        });
+  }
+
   private ElementImpl elementFromMappedRanges(
-      List<MappedRange> mappedRangesForElement, RetraceClassElementImpl classElement) {
-    MappedRange topFrame = mappedRangesForElement.get(0);
+      List<MappedRangeForFrame> mappedRangesForElement, RetraceClassElementImpl classElement) {
+    MappedRangeForFrame topFrame = mappedRangesForElement.get(0);
     MethodReference methodReference =
         methodReferenceFromMappedRange(
-            topFrame, classElement.getRetracedClass().getClassReference());
+            topFrame.mappedRange, classElement.getRetracedClass().getClassReference());
     return new ElementImpl(
         this,
         classElement,
         getRetracedMethod(methodReference, topFrame, obfuscatedPosition),
         mappedRangesForElement,
         obfuscatedPosition,
-        retracer,
-        context);
+        retracer);
   }
 
   private RetracedMethodReferenceImpl getRetracedMethod(
-      MethodReference methodReference, MappedRange mappedRange, OptionalInt obfuscatedPosition) {
-    if (mappedRange.minifiedRange == null
-        || (obfuscatedPosition.orElse(-1) == -1 && !isAmbiguous())) {
+      MethodReference methodReference,
+      MappedRangeForFrame mappedRangeForFrame,
+      OptionalInt obfuscatedPosition) {
+    MappedRange mappedRange = mappedRangeForFrame.mappedRange;
+    OptionalInt originalPosition = mappedRangeForFrame.position;
+    if (!isAmbiguous()
+        && (mappedRange.minifiedRange == null || obfuscatedPosition.orElse(-1) == -1)) {
       int originalLineNumber = mappedRange.getFirstLineNumberOfOriginalRange();
       if (originalLineNumber > 0) {
-        return RetracedMethodReferenceImpl.create(methodReference, originalLineNumber);
+        return RetracedMethodReferenceImpl.create(
+            methodReference, OptionalUtils.orElse(originalPosition, originalLineNumber));
       } else {
-        return RetracedMethodReferenceImpl.create(methodReference);
+        return RetracedMethodReferenceImpl.create(methodReference, originalPosition);
       }
     }
     if (!obfuscatedPosition.isPresent()
+        || mappedRange.minifiedRange == null
         || !mappedRange.minifiedRange.contains(obfuscatedPosition.getAsInt())) {
-      return RetracedMethodReferenceImpl.create(methodReference);
+      return RetracedMethodReferenceImpl.create(methodReference, originalPosition);
     }
     return RetracedMethodReferenceImpl.create(
-        methodReference, mappedRange.getOriginalLineNumber(obfuscatedPosition.getAsInt()));
+        methodReference,
+        OptionalUtils.orElseGet(
+            originalPosition,
+            () -> mappedRange.getOriginalLineNumber(obfuscatedPosition.getAsInt())));
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return !mappedRanges.isEmpty();
   }
 
   public static class ElementImpl implements RetraceFrameElement {
@@ -160,33 +229,30 @@
     private final RetracedMethodReferenceImpl methodReference;
     private final RetraceFrameResultImpl retraceFrameResult;
     private final RetraceClassElementImpl classElement;
-    private final List<MappedRange> mappedRanges;
+    private final List<MappedRangeForFrame> mappedRanges;
     private final OptionalInt obfuscatedPosition;
     private final RetracerImpl retracer;
-    private final RetraceStackTraceContextImpl context;
 
     ElementImpl(
         RetraceFrameResultImpl retraceFrameResult,
         RetraceClassElementImpl classElement,
         RetracedMethodReferenceImpl methodReference,
-        List<MappedRange> mappedRanges,
+        List<MappedRangeForFrame> mappedRanges,
         OptionalInt obfuscatedPosition,
-        RetracerImpl retracer,
-        RetraceStackTraceContextImpl context) {
+        RetracerImpl retracer) {
       this.methodReference = methodReference;
       this.retraceFrameResult = retraceFrameResult;
       this.classElement = classElement;
       this.mappedRanges = mappedRanges;
       this.obfuscatedPosition = obfuscatedPosition;
       this.retracer = retracer;
-      this.context = context;
     }
 
     private boolean isOuterMostFrameCompilerSynthesized() {
       if (mappedRanges == null || mappedRanges.isEmpty()) {
         return false;
       }
-      return ListUtils.last(mappedRanges).isCompilerSynthesized();
+      return ListUtils.last(mappedRanges).mappedRange.isCompilerSynthesized();
     }
 
     /**
@@ -246,9 +312,10 @@
       RetraceStackTraceCurrentEvaluationInformation currentFrameInformation =
           context == null
               ? RetraceStackTraceCurrentEvaluationInformation.empty()
-              : contextImpl.computeRewritingInformation(mappedRanges);
+              : contextImpl.computeRewriteFrameInformation(
+                  ListUtils.map(mappedRanges, MappedRangeForFrame::getMappedRange));
       int index = 0;
-      int numberOfFramesToRemove = currentFrameInformation.getRemoveInnerFrames();
+      int numberOfFramesToRemove = currentFrameInformation.getRemoveInnerFramesCount();
       int totalNumberOfFrames =
           (mappedRanges == null || mappedRanges.isEmpty()) ? 1 : mappedRanges.size();
       if (numberOfFramesToRemove > totalNumberOfFrames) {
@@ -298,17 +365,52 @@
       return outerFrames;
     }
 
-    private RetracedMethodReferenceImpl getMethodReferenceFromMappedRange(MappedRange mappedRange) {
+    private RetracedMethodReferenceImpl getMethodReferenceFromMappedRange(
+        MappedRangeForFrame mappedRangeForFrame) {
       MethodReference methodReference =
           methodReferenceFromMappedRange(
-              mappedRange, classElement.getRetracedClass().getClassReference());
-      return retraceFrameResult.getRetracedMethod(methodReference, mappedRange, obfuscatedPosition);
+              mappedRangeForFrame.getMappedRange(),
+              classElement.getRetracedClass().getClassReference());
+      return retraceFrameResult.getRetracedMethod(
+          methodReference, mappedRangeForFrame, obfuscatedPosition);
     }
 
     @Override
-    public RetraceStackTraceContext getContext() {
-      // This will change when supporting outline frames.
-      return RetraceStackTraceContext.empty();
+    public RetraceStackTraceContext getRetraceStackTraceContext() {
+      if (mappedRanges == null
+          || mappedRanges.isEmpty()
+          || !obfuscatedPosition.isPresent()
+          || !ListUtils.last(mappedRanges).getMappedRange().isOutlineFrame()) {
+        return RetraceStackTraceContext.empty();
+      }
+      return RetraceStackTraceContextImpl.builder().setRewritePosition(obfuscatedPosition).build();
+    }
+  }
+
+  private static class MappedRangeForFrame {
+
+    private final MappedRange mappedRange;
+    private final OptionalInt position;
+
+    private MappedRangeForFrame(MappedRange mappedRange, OptionalInt position) {
+      this.mappedRange = mappedRange;
+      this.position = position;
+    }
+
+    private MappedRange getMappedRange() {
+      return mappedRange;
+    }
+
+    private static MappedRangeForFrame create(MappedRange mappedRange) {
+      return create(
+          mappedRange,
+          mappedRange.originalRange == null || mappedRange.originalRange.span() != 1
+              ? OptionalInt.empty()
+              : OptionalInt.of(mappedRange.originalRange.from));
+    }
+
+    private static MappedRangeForFrame create(MappedRange mappedRange, OptionalInt position) {
+      return new MappedRangeForFrame(mappedRange, position);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
index 978a933..60a6967 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
+import com.android.tools.r8.naming.mappinginformation.OutlineCallsiteMappingInformation;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.retrace.RetraceMethodElement;
 import com.android.tools.r8.retrace.RetraceMethodResult;
@@ -14,11 +15,13 @@
 import com.android.tools.r8.retrace.RetracedMethodReference;
 import com.android.tools.r8.retrace.RetracedSourceFile;
 import com.android.tools.r8.retrace.internal.RetraceClassResultImpl.RetraceClassElementImpl;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Pair;
-import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.OptionalInt;
+import java.util.Set;
 import java.util.stream.Stream;
 
 public class RetraceMethodResultImpl implements RetraceMethodResult {
@@ -62,38 +65,69 @@
   }
 
   @Override
-  public RetraceFrameResultImpl narrowByPosition(RetraceStackTraceContext context, int position) {
+  public boolean isEmpty() {
+    return mappedRanges == null || mappedRanges.isEmpty();
+  }
+
+  @Override
+  public RetraceFrameResultImpl narrowByPosition(
+      RetraceStackTraceContext context, OptionalInt position) {
     List<Pair<RetraceClassElementImpl, List<MappedRange>>> narrowedRanges = new ArrayList<>();
-    List<Pair<RetraceClassElementImpl, List<MappedRange>>> noMappingRanges = new ArrayList<>();
+    RetraceStackTraceContextImpl stackTraceContext = null;
+    if (context instanceof RetraceStackTraceContextImpl) {
+      stackTraceContext = (RetraceStackTraceContextImpl) context;
+    }
     for (Pair<RetraceClassElementImpl, List<MappedRange>> mappedRange : mappedRanges) {
       if (mappedRange.getSecond() == null) {
-        noMappingRanges.add(new Pair<>(mappedRange.getFirst(), null));
+        narrowedRanges.add(new Pair<>(mappedRange.getFirst(), null));
         continue;
       }
-      List<MappedRange> ranges =
-          new MappedRangesOfName(mappedRange.getSecond()).allRangesForLine(position, false);
-      boolean hasAddedRanges = false;
-      if (!ranges.isEmpty()) {
-        narrowedRanges.add(new Pair<>(mappedRange.getFirst(), ranges));
-        hasAddedRanges = true;
-      } else {
-        narrowedRanges = new ArrayList<>();
-        for (MappedRange mapped : mappedRange.getSecond()) {
-          if (mapped.minifiedRange == null) {
-            narrowedRanges.add(new Pair<>(mappedRange.getFirst(), ImmutableList.of(mapped)));
-            hasAddedRanges = true;
+      MappedRangesOfName mappedRangesOfElement = new MappedRangesOfName(mappedRange.getSecond());
+      List<MappedRange> mappedRangesForPosition = null;
+      boolean hasPosition = position.isPresent() && position.getAsInt() >= 0;
+      if (hasPosition) {
+        mappedRangesForPosition =
+            mappedRangesOfElement.allRangesForLine(position.getAsInt(), false);
+      }
+      if (mappedRangesForPosition == null || mappedRangesForPosition.isEmpty()) {
+        mappedRangesForPosition =
+            hasPosition
+                ? ListUtils.filter(
+                    mappedRangesOfElement.getMappedRanges(), range -> range.minifiedRange == null)
+                : mappedRangesOfElement.getMappedRanges();
+      }
+      if (mappedRangesForPosition != null && !mappedRangesForPosition.isEmpty()) {
+        if (stackTraceContext != null && stackTraceContext.hasRewritePosition()) {
+          List<OutlineCallsiteMappingInformation> outlineCallsiteInformation =
+              ListUtils.last(mappedRangesForPosition).getOutlineCallsiteInformation();
+          if (!outlineCallsiteInformation.isEmpty()) {
+            assert outlineCallsiteInformation.size() == 1
+                : "There can only be one outline entry for a line";
+            return narrowByPosition(
+                stackTraceContext.buildFromThis().clearRewritePosition().build(),
+                OptionalInt.of(
+                    outlineCallsiteInformation
+                        .get(0)
+                        .rewritePosition(stackTraceContext.getRewritePosition())));
           }
         }
-      }
-      if (!hasAddedRanges) {
-        narrowedRanges.add(new Pair<>(mappedRange.getFirst(), null));
+        // Mapped ranges can have references to overloaded signatures. We distinguish those by
+        // looking at the cardinal mapping range.
+        for (MappedRange mappedRangeForPosition : mappedRangesForPosition) {
+          if (narrowedRanges.isEmpty()
+              || mappedRangeForPosition.originalRange == null
+              || !mappedRangeForPosition.originalRange.isCardinal) {
+            narrowedRanges.add(new Pair<>(mappedRange.getFirst(), new ArrayList<>()));
+          }
+          ListUtils.last(narrowedRanges).getSecond().add(mappedRangeForPosition);
+        }
       }
     }
     return new RetraceFrameResultImpl(
         classResult,
-        narrowedRanges.isEmpty() ? noMappingRanges : narrowedRanges,
+        narrowedRanges,
         methodDefinition,
-        OptionalInt.of(position),
+        position,
         retracer,
         (RetraceStackTraceContextImpl) context);
   }
@@ -114,17 +148,19 @@
                             methodDefinition.substituteHolder(
                                 classElement.getRetracedClass().getClassReference()))));
               }
-              return mappedRanges.stream()
-                  .map(
-                      mappedRange -> {
-                        MethodReference methodReference =
-                            RetraceUtils.methodReferenceFromMappedRange(
-                                mappedRange, classElement.getRetracedClass().getClassReference());
-                        return new ElementImpl(
-                            this,
-                            classElement,
-                            RetracedMethodReferenceImpl.create(methodReference));
-                      });
+              List<ElementImpl> results = new ArrayList<>();
+              Set<MethodReference> seenMethodReferences = new HashSet<>();
+              for (MappedRange mappedRange : mappedRanges) {
+                MethodReference methodReference =
+                    RetraceUtils.methodReferenceFromMappedRange(
+                        mappedRange, classElement.getRetracedClass().getClassReference());
+                if (seenMethodReferences.add(methodReference)) {
+                  results.add(
+                      new ElementImpl(
+                          this, classElement, RetracedMethodReferenceImpl.create(methodReference)));
+                }
+              }
+              return results.stream();
             });
   }
 
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceContextImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceContextImpl.java
index 31533d4..94e2d31 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceContextImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceContextImpl.java
@@ -12,20 +12,24 @@
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.utils.ListUtils;
 import java.util.List;
+import java.util.OptionalInt;
 
 public class RetraceStackTraceContextImpl implements RetraceStackTraceContext {
 
   private final ClassReference thrownException;
+  private final OptionalInt rewritePosition;
 
-  private RetraceStackTraceContextImpl(ClassReference thrownException) {
+  private RetraceStackTraceContextImpl(
+      ClassReference thrownException, OptionalInt rewritePosition) {
     this.thrownException = thrownException;
+    this.rewritePosition = rewritePosition;
   }
 
   public ClassReference getThrownException() {
     return thrownException;
   }
 
-  RetraceStackTraceCurrentEvaluationInformation computeRewritingInformation(
+  RetraceStackTraceCurrentEvaluationInformation computeRewriteFrameInformation(
       List<MappedRange> mappedRanges) {
     if (mappedRanges == null || mappedRanges.isEmpty()) {
       return RetraceStackTraceCurrentEvaluationInformation.empty();
@@ -44,6 +48,14 @@
     return builder.build();
   }
 
+  public boolean hasRewritePosition() {
+    return rewritePosition.isPresent();
+  }
+
+  public int getRewritePosition() {
+    return rewritePosition.getAsInt();
+  }
+
   private boolean evaluateConditions(List<Condition> conditions) {
     for (Condition condition : conditions) {
       if (!condition.evaluate(this)) {
@@ -57,9 +69,14 @@
     return Builder.create();
   }
 
+  public Builder buildFromThis() {
+    return builder().setThrownException(thrownException).setRewritePosition(rewritePosition);
+  }
+
   public static class Builder {
 
     private ClassReference thrownException;
+    private OptionalInt rewritePosition = OptionalInt.empty();
 
     private Builder() {}
 
@@ -68,8 +85,18 @@
       return this;
     }
 
+    public Builder setRewritePosition(OptionalInt rewritePosition) {
+      this.rewritePosition = rewritePosition;
+      return this;
+    }
+
+    public Builder clearRewritePosition() {
+      this.rewritePosition = OptionalInt.empty();
+      return this;
+    }
+
     public RetraceStackTraceContextImpl build() {
-      return new RetraceStackTraceContextImpl(thrownException);
+      return new RetraceStackTraceContextImpl(thrownException, rewritePosition);
     }
 
     public static Builder create() {
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceCurrentEvaluationInformation.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceCurrentEvaluationInformation.java
index b25454f..275b72e 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceCurrentEvaluationInformation.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceCurrentEvaluationInformation.java
@@ -9,14 +9,14 @@
   private static final RetraceStackTraceCurrentEvaluationInformation EMPTY =
       new RetraceStackTraceCurrentEvaluationInformation(0);
 
-  private final int removeInnerFrames;
+  private final int removeInnerFramesCount;
 
-  private RetraceStackTraceCurrentEvaluationInformation(int removeInnerFrames) {
-    this.removeInnerFrames = removeInnerFrames;
+  private RetraceStackTraceCurrentEvaluationInformation(int removeInnerFramesCount) {
+    this.removeInnerFramesCount = removeInnerFramesCount;
   }
 
-  public int getRemoveInnerFrames() {
-    return removeInnerFrames;
+  public int getRemoveInnerFramesCount() {
+    return removeInnerFramesCount;
   }
 
   public static RetraceStackTraceCurrentEvaluationInformation empty() {
@@ -30,12 +30,22 @@
   public static class Builder {
 
     private int removeInnerFramesCount;
+    private int position;
 
     public Builder incrementRemoveInnerFramesCount(int increment) {
       removeInnerFramesCount += increment;
       return this;
     }
 
+    public int getPosition() {
+      return position;
+    }
+
+    public Builder setPosition(int position) {
+      this.position = position;
+      return this;
+    }
+
     RetraceStackTraceCurrentEvaluationInformation build() {
       return new RetraceStackTraceCurrentEvaluationInformation(removeInnerFramesCount);
     }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceElementProxyResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceElementProxyResultImpl.java
new file mode 100644
index 0000000..1317d9f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceStackTraceElementProxyResultImpl.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.internal;
+
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.RetraceStackTraceElementProxyResult;
+import com.android.tools.r8.retrace.StackTraceElementProxy;
+import com.android.tools.r8.retrace.internal.StackTraceElementProxyRetracerImpl.RetraceStackTraceElementProxyImpl;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+public class RetraceStackTraceElementProxyResultImpl<T, ST extends StackTraceElementProxy<T, ST>>
+    implements RetraceStackTraceElementProxyResult<T, ST> {
+
+  private final Stream<? extends RetraceStackTraceElementProxyImpl<T, ST>> resultStream;
+  private final Supplier<RetraceStackTraceContext> resultContext;
+
+  private RetraceStackTraceElementProxyResultImpl(
+      Stream<? extends RetraceStackTraceElementProxyImpl<T, ST>> resultStream,
+      Supplier<RetraceStackTraceContext> resultContext) {
+    this.resultStream = resultStream;
+    this.resultContext = resultContext;
+  }
+
+  @Override
+  public Stream<? extends RetraceStackTraceElementProxyImpl<T, ST>> stream() {
+    return resultStream;
+  }
+
+  @Override
+  public RetraceStackTraceContext getResultContext() {
+    return resultContext.get();
+  }
+
+  Builder<T, ST> builder() {
+    return Builder.<T, ST>create().setResultStream(resultStream).setResultContext(resultContext);
+  }
+
+  static class Builder<T, ST extends StackTraceElementProxy<T, ST>> {
+
+    Stream<? extends RetraceStackTraceElementProxyImpl<T, ST>> resultStream;
+    Supplier<RetraceStackTraceContext> resultContext;
+
+    private Builder() {}
+
+    Builder<T, ST> setResultStream(
+        Stream<? extends RetraceStackTraceElementProxyImpl<T, ST>> resultStream) {
+      this.resultStream = resultStream;
+      return this;
+    }
+
+    Builder<T, ST> setResultContext(Supplier<RetraceStackTraceContext> resultContext) {
+      this.resultContext = resultContext;
+      return this;
+    }
+
+    RetraceStackTraceElementProxyResultImpl<T, ST> build() {
+      return new RetraceStackTraceElementProxyResultImpl<>(resultStream, resultContext);
+    }
+
+    static <T, ST extends StackTraceElementProxy<T, ST>> Builder<T, ST> create() {
+      return new Builder<>();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceThrownExceptionResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceThrownExceptionResultImpl.java
index aa2c1f0..bb49d70 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceThrownExceptionResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceThrownExceptionResultImpl.java
@@ -34,6 +34,11 @@
     return Stream.of(createElement());
   }
 
+  @Override
+  public boolean isEmpty() {
+    return obfuscatedReference == null;
+  }
+
   private RetraceThrownExceptionElement createElement() {
     return new RetraceThrownExceptionElementImpl(
         this,
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodReferenceImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodReferenceImpl.java
index 49061b2..f896692 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodReferenceImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodReferenceImpl.java
@@ -12,10 +12,10 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.OptionalInt;
 
 public abstract class RetracedMethodReferenceImpl implements RetracedMethodReference {
 
-  private static final int NO_POSITION = -1;
   private static final Comparator<RetracedMethodReference> comparator =
       Comparator.comparing(RetracedMethodReference::getMethodName)
           .thenComparing(RetracedMethodReference::isKnown)
@@ -34,7 +34,21 @@
                       ComparatorUtils.listComparator(
                           Comparator.comparing(TypeReference::getTypeName))));
 
-  private RetracedMethodReferenceImpl() {}
+  protected final OptionalInt position;
+
+  private RetracedMethodReferenceImpl(OptionalInt position) {
+    this.position = position;
+  }
+
+  @Override
+  public boolean hasPosition() {
+    return position.isPresent();
+  }
+
+  @Override
+  public int getOriginalPositionOrDefault(int defaultPosition) {
+    return position.orElse(defaultPosition);
+  }
 
   @Override
   public boolean isUnknown() {
@@ -60,12 +74,12 @@
       implements KnownRetracedMethodReference {
 
     private final MethodReference methodReference;
-    private final int position;
 
-    private KnownRetracedMethodReferenceImpl(MethodReference methodReference, int position) {
+    private KnownRetracedMethodReferenceImpl(
+        MethodReference methodReference, OptionalInt position) {
+      super(position);
       assert methodReference != null;
       this.methodReference = methodReference;
-      this.position = position;
     }
 
     @Override
@@ -94,16 +108,6 @@
     }
 
     @Override
-    public boolean hasPosition() {
-      return position != NO_POSITION;
-    }
-
-    @Override
-    public int getOriginalPositionOrDefault(int defaultPosition) {
-      return hasPosition() ? position : defaultPosition;
-    }
-
-    @Override
     public TypeReference getReturnType() {
       assert !isVoid();
       return methodReference.getReturnType();
@@ -140,11 +144,11 @@
   public static final class UnknownRetracedMethodReferenceImpl extends RetracedMethodReferenceImpl {
 
     private final MethodDefinition methodDefinition;
-    private final int position;
 
-    private UnknownRetracedMethodReferenceImpl(MethodDefinition methodDefinition, int position) {
+    private UnknownRetracedMethodReferenceImpl(
+        MethodDefinition methodDefinition, OptionalInt position) {
+      super(position);
       this.methodDefinition = methodDefinition;
-      this.position = position;
     }
 
     @Override
@@ -157,16 +161,6 @@
       return methodDefinition.getName();
     }
 
-    @Override
-    public boolean hasPosition() {
-      return position != NO_POSITION;
-    }
-
-    @Override
-    public int getOriginalPositionOrDefault(int defaultPosition) {
-      return hasPosition() ? position : defaultPosition;
-    }
-
     public Optional<MethodReference> getMethodReference() {
       if (!methodDefinition.isFullMethodDefinition()) {
         return Optional.empty();
@@ -176,22 +170,19 @@
   }
 
   static RetracedMethodReferenceImpl create(MethodDefinition methodDefinition) {
-    return create(methodDefinition, NO_POSITION);
+    if (methodDefinition.isFullMethodDefinition()) {
+      return create(
+          methodDefinition.asFullMethodDefinition().getMethodReference(), OptionalInt.empty());
+    }
+    return new UnknownRetracedMethodReferenceImpl(methodDefinition, OptionalInt.empty());
   }
 
-  static RetracedMethodReferenceImpl create(MethodDefinition methodDefinition, int position) {
-    if (methodDefinition.isFullMethodDefinition()) {
-      return new KnownRetracedMethodReferenceImpl(
-          methodDefinition.asFullMethodDefinition().getMethodReference(), position);
-    }
-    return new UnknownRetracedMethodReferenceImpl(methodDefinition, position);
-  }
 
   static RetracedMethodReferenceImpl create(MethodReference methodReference) {
-    return create(methodReference, NO_POSITION);
+    return create(methodReference, OptionalInt.empty());
   }
 
-  static RetracedMethodReferenceImpl create(MethodReference methodReference, int position) {
+  static RetracedMethodReferenceImpl create(MethodReference methodReference, OptionalInt position) {
     return new KnownRetracedMethodReferenceImpl(methodReference, position);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
index 9aadbb3..563de26 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.Retracer;
 import java.io.BufferedReader;
+import java.util.OptionalInt;
 
 /** A default implementation for the retrace api using the ClassNameMapper defined in R8. */
 public class RetracerImpl implements Retracer {
@@ -62,13 +63,13 @@
   }
 
   @Override
-  public RetraceFrameResult retraceFrame(MethodReference methodReference, int position) {
+  public RetraceFrameResult retraceFrame(MethodReference methodReference, OptionalInt position) {
     return retraceFrame(methodReference, position, RetraceStackTraceContext.empty());
   }
 
   @Override
   public RetraceFrameResult retraceFrame(
-      MethodReference methodReference, int position, RetraceStackTraceContext context) {
+      MethodReference methodReference, OptionalInt position, RetraceStackTraceContext context) {
     return retraceClass(methodReference.getHolderClass())
         .lookupMethod(methodReference.getMethodName())
         .narrowByPosition(context, position);
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
index 8040744..987aead 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
@@ -7,26 +7,33 @@
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.retrace.RetraceClassResult;
+import com.android.tools.r8.retrace.RetraceFieldElement;
 import com.android.tools.r8.retrace.RetraceFieldResult;
+import com.android.tools.r8.retrace.RetraceFrameElement;
 import com.android.tools.r8.retrace.RetraceFrameResult;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.RetraceStackTraceElementProxy;
+import com.android.tools.r8.retrace.RetraceStackTraceElementProxyResult;
+import com.android.tools.r8.retrace.RetraceThrownExceptionElement;
 import com.android.tools.r8.retrace.RetraceTypeResult;
 import com.android.tools.r8.retrace.RetraceTypeResult.Element;
 import com.android.tools.r8.retrace.RetracedClassReference;
 import com.android.tools.r8.retrace.RetracedFieldReference;
 import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.retrace.RetracedSingleFrame;
 import com.android.tools.r8.retrace.RetracedSourceFile;
 import com.android.tools.r8.retrace.RetracedTypeReference;
 import com.android.tools.r8.retrace.Retracer;
 import com.android.tools.r8.retrace.StackTraceElementProxy;
 import com.android.tools.r8.retrace.StackTraceElementProxyRetracer;
+import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.ListUtils;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.OptionalInt;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -40,185 +47,259 @@
   }
 
   @Override
-  public Stream<? extends RetraceStackTraceElementProxy<T, ST>> retrace(
+  public RetraceStackTraceElementProxyResult<T, ST> retrace(
       ST element, RetraceStackTraceContext context) {
-    Stream<RetraceStackTraceElementProxyImpl<T, ST>> currentResults =
-        Stream.of(RetraceStackTraceElementProxyImpl.create(element, context));
+    RetraceStackTraceElementProxyResultImpl<T, ST> currentResult =
+        RetraceStackTraceElementProxyResultImpl.Builder.<T, ST>create()
+            .setResultStream(Stream.of(RetraceStackTraceElementProxyImpl.create(element, context)))
+            .setResultContext(RetraceStackTraceContext::empty)
+            .build();
     if (!element.hasClassName()
         && !element.hasFieldOrReturnType()
         && !element.hasMethodArguments()) {
-      return currentResults;
+      return currentResult;
     }
-    currentResults = retraceFieldOrReturnType(currentResults, element);
-    currentResults = retracedMethodArguments(currentResults, element);
+    currentResult = retraceFieldOrReturnType(currentResult, element);
+    currentResult = retracedMethodArguments(currentResult, element);
     if (element.hasClassName()) {
       RetraceClassResult classResult = retracer.retraceClass(element.getClassReference());
       if (element.hasMethodName()) {
-        currentResults = retraceMethod(currentResults, element, classResult);
+        currentResult = retraceMethod(currentResult, element, classResult, context);
       } else if (element.hasFieldName()) {
-        currentResults = retraceField(currentResults, element, classResult);
+        currentResult = retraceField(currentResult, element, classResult);
       } else {
-        currentResults = retraceClassOrType(currentResults, element, classResult);
+        currentResult = retraceClassOrType(currentResult, classResult);
       }
     }
-    return currentResults;
+    return currentResult;
   }
 
-  private Stream<RetraceStackTraceElementProxyImpl<T, ST>> retraceClassOrType(
-      Stream<RetraceStackTraceElementProxyImpl<T, ST>> currentResults,
-      ST element,
+  private RetraceStackTraceElementProxyResultImpl<T, ST> retraceClassOrType(
+      RetraceStackTraceElementProxyResultImpl<T, ST> currentResult,
       RetraceClassResult classResult) {
-    return currentResults.flatMap(
-        proxy ->
-            // We assume, since no method was defined for this stack trace element, that this was a
-            // thrown exception.
-            classResult.lookupThrownException(proxy.getContext()).stream()
-                .map(
-                    thrownExceptionElement ->
-                        proxy
-                            .builder()
-                            .setRetracedClass(thrownExceptionElement.getRetracedClass())
-                            .joinAmbiguous(classResult.isAmbiguous())
-                            .setTopFrame(true)
-                            .setContext(thrownExceptionElement.getContext())
-                            .applyIf(
-                                element.hasSourceFile(),
-                                builder -> {
-                                  RetracedSourceFile sourceFile =
-                                      thrownExceptionElement.getSourceFile();
-                                  builder.setSourceFile(
-                                      sourceFile.hasRetraceResult()
-                                          ? sourceFile.getSourceFile()
-                                          : RetraceUtils.inferSourceFile(
-                                              thrownExceptionElement
-                                                  .getRetracedClass()
-                                                  .getTypeName(),
-                                              element.getSourceFile(),
-                                              classResult.hasRetraceResult()));
-                                })
-                            .build()));
+    return currentResult
+        .builder()
+        .setResultStream(
+            currentResult.stream()
+                .flatMap(
+                    proxy ->
+                        // We assume, since no method was defined for this stack trace element, that
+                        // this was a thrown exception.
+                        classResult.lookupThrownException(proxy.getContext()).stream()
+                            .map(
+                                thrownExceptionElement ->
+                                    buildProxyForRewrittenThrownExceptionElement(
+                                        classResult, proxy, thrownExceptionElement))))
+        .build();
   }
 
-  private Stream<RetraceStackTraceElementProxyImpl<T, ST>> retraceMethod(
-      Stream<RetraceStackTraceElementProxyImpl<T, ST>> currentResults,
+  private RetraceStackTraceElementProxyImpl<T, ST> buildProxyForRewrittenThrownExceptionElement(
+      RetraceClassResult classResult,
+      RetraceStackTraceElementProxyImpl<T, ST> proxy,
+      RetraceThrownExceptionElement thrownExceptionElement) {
+    return proxy
+        .builder()
+        .setRetracedClass(thrownExceptionElement.getRetracedClass())
+        .joinAmbiguous(classResult.isAmbiguous())
+        .setTopFrame(true)
+        .setContext(thrownExceptionElement.getContext())
+        .apply(
+            setSourceFileOnProxy(
+                thrownExceptionElement::getSourceFile,
+                thrownExceptionElement.getRetracedClass(),
+                classResult))
+        .build();
+  }
+
+  private RetraceStackTraceElementProxyResultImpl<T, ST> retraceMethod(
+      RetraceStackTraceElementProxyResultImpl<T, ST> currentResult,
+      ST element,
+      RetraceClassResult classResult,
+      RetraceStackTraceContext context) {
+    Box<RetraceStackTraceContext> resultingContext = new Box<>(RetraceStackTraceContext.empty());
+    RetraceStackTraceElementProxyResultImpl.Builder<T, ST> resultBuilder =
+        currentResult.builder().setResultContext(resultingContext::get);
+    return resultBuilder
+        .setResultStream(
+            currentResult.stream()
+                .flatMap(
+                    proxy -> {
+                      RetraceFrameResult frameResult =
+                          classResult.lookupFrame(
+                              context,
+                              element.hasLineNumber()
+                                  ? OptionalInt.of(element.getLineNumber())
+                                  : OptionalInt.empty(),
+                              element.getMethodName());
+                      if (!frameResult.isEmpty()) {
+                        return classResult.stream()
+                            .map(
+                                classElement ->
+                                    proxy
+                                        .builder()
+                                        .setTopFrame(true)
+                                        .joinAmbiguous(classResult.isAmbiguous())
+                                        .setRetracedClass(classElement.getRetracedClass())
+                                        .applyIf(
+                                            element.hasLineNumber(),
+                                            b -> b.setLineNumber(element.getLineNumber()))
+                                        .apply(
+                                            setSourceFileOnProxy(
+                                                classElement::getSourceFile,
+                                                classElement.getRetracedClass(),
+                                                classResult))
+                                        .build());
+                      }
+                      return frameResult.stream()
+                          .flatMap(
+                              frameElement -> {
+                                resultingContext.set(frameElement.getRetraceStackTraceContext());
+                                return frameElement
+                                    .streamRewritten(context)
+                                    .map(
+                                        singleFrame ->
+                                            buildProxyForRewrittenFrameElement(
+                                                element,
+                                                classResult,
+                                                proxy,
+                                                frameResult,
+                                                frameElement,
+                                                singleFrame));
+                              });
+                    }))
+        .build();
+  }
+
+  private RetraceStackTraceElementProxyImpl<T, ST> buildProxyForRewrittenFrameElement(
+      ST element,
+      RetraceClassResult classResult,
+      RetraceStackTraceElementProxyImpl<T, ST> proxy,
+      RetraceFrameResult frameResult,
+      RetraceFrameElement frameElement,
+      RetracedSingleFrame singleFrame) {
+    boolean isTopFrame = singleFrame.getIndex() == 0;
+    RetracedMethodReference method = singleFrame.getMethodReference();
+    return proxy
+        .builder()
+        .setRetracedClass(method.getHolderClass())
+        .setRetracedMethod(method)
+        .joinAmbiguous(frameResult.isAmbiguous())
+        .setTopFrame(isTopFrame)
+        .setContext(frameElement.getRetraceStackTraceContext())
+        .applyIf(
+            element.hasLineNumber(),
+            builder -> {
+              builder.setLineNumber(method.getOriginalPositionOrDefault(element.getLineNumber()));
+            })
+        .apply(
+            setSourceFileOnProxy(
+                () -> frameElement.getSourceFile(method), method.getHolderClass(), classResult))
+        .build();
+  }
+
+  private RetraceStackTraceElementProxyResultImpl<T, ST> retraceField(
+      RetraceStackTraceElementProxyResultImpl<T, ST> currentResult,
       ST element,
       RetraceClassResult classResult) {
-    return currentResults.flatMap(
-        proxy -> {
-          RetraceFrameResult frameResult =
-              classResult.lookupFrame(
-                  proxy.context,
-                  element.hasLineNumber()
-                      ? OptionalInt.of(element.getLineNumber())
-                      : OptionalInt.empty(),
-                  element.getMethodName());
-          return frameResult.stream()
-              .flatMap(
-                  frameElement ->
-                      frameElement
-                          .streamRewritten(proxy.getContext())
+    return currentResult
+        .builder()
+        .setResultStream(
+            currentResult.stream()
+                .flatMap(
+                    proxy -> {
+                      RetraceFieldResult retraceFieldResult =
+                          classResult.lookupField(element.getFieldName());
+                      return retraceFieldResult.stream()
                           .map(
-                              singleFrame -> {
-                                boolean isTopFrame = singleFrame.getIndex() == 0;
-                                RetracedMethodReference method = singleFrame.getMethodReference();
-                                return proxy
-                                    .builder()
-                                    .setRetracedClass(method.getHolderClass())
-                                    .setRetracedMethod(method)
-                                    .joinAmbiguous(frameResult.isAmbiguous() && isTopFrame)
-                                    .setTopFrame(isTopFrame)
-                                    .setContext(frameElement.getContext())
-                                    .applyIf(
-                                        element.hasLineNumber(),
-                                        builder -> {
-                                          builder.setLineNumber(
-                                              method.getOriginalPositionOrDefault(
-                                                  element.getLineNumber()));
-                                        })
-                                    .applyIf(
-                                        element.hasSourceFile(),
-                                        builder -> {
-                                          RetracedSourceFile sourceFileResult =
-                                              frameElement.getSourceFile(method);
-                                          builder.setSourceFile(
-                                              sourceFileResult.hasRetraceResult()
-                                                  ? sourceFileResult.getSourceFile()
-                                                  : RetraceUtils.inferSourceFile(
-                                                      method.getHolderClass().getTypeName(),
-                                                      element.getSourceFile(),
-                                                      classResult.hasRetraceResult()));
-                                        })
-                                    .build();
-                              }));
-        });
+                              fieldElement ->
+                                  buildProxyForRewrittenFieldElement(
+                                      classResult, proxy, retraceFieldResult, fieldElement));
+                    }))
+        .build();
   }
 
-  private Stream<RetraceStackTraceElementProxyImpl<T, ST>> retraceField(
-      Stream<RetraceStackTraceElementProxyImpl<T, ST>> currentResults,
-      ST element,
+  private RetraceStackTraceElementProxyImpl<T, ST> buildProxyForRewrittenFieldElement(
+      RetraceClassResult classResult,
+      RetraceStackTraceElementProxyImpl<T, ST> proxy,
+      RetraceFieldResult retraceFieldResult,
+      RetraceFieldElement fieldElement) {
+    return proxy
+        .builder()
+        .setRetracedClass(fieldElement.getField().getHolderClass())
+        .setRetracedField(fieldElement.getField())
+        .joinAmbiguous(retraceFieldResult.isAmbiguous())
+        .setTopFrame(true)
+        .apply(
+            setSourceFileOnProxy(
+                fieldElement::getSourceFile, fieldElement.getField().getHolderClass(), classResult))
+        .build();
+  }
+
+  private Consumer<RetraceStackTraceElementProxyImpl.Builder<T, ST>> setSourceFileOnProxy(
+      Supplier<RetracedSourceFile> sourceFile,
+      RetracedClassReference classReference,
       RetraceClassResult classResult) {
-    return currentResults.flatMap(
-        proxy -> {
-          RetraceFieldResult retraceFieldResult = classResult.lookupField(element.getFieldName());
-          return retraceFieldResult.stream()
-              .map(
-                  fieldElement ->
-                      proxy
-                          .builder()
-                          .setRetracedClass(fieldElement.getField().getHolderClass())
-                          .setRetracedField(fieldElement.getField())
-                          .joinAmbiguous(retraceFieldResult.isAmbiguous())
-                          .setTopFrame(true)
-                          .applyIf(
-                              element.hasSourceFile(),
-                              builder -> {
-                                RetracedSourceFile sourceFile = fieldElement.getSourceFile();
-                                builder.setSourceFile(
-                                    sourceFile.hasRetraceResult()
-                                        ? sourceFile.getSourceFile()
-                                        : RetraceUtils.inferSourceFile(
-                                            fieldElement.getField().getHolderClass().getTypeName(),
-                                            element.getSourceFile(),
-                                            classResult.hasRetraceResult()));
-                              })
-                          .build());
-        });
+    return proxy -> {
+      ST original = proxy.originalElement;
+      if (!original.hasSourceFile()) {
+        return;
+      }
+      RetracedSourceFile retracedSourceFile = sourceFile.get();
+      proxy.setSourceFile(
+          retracedSourceFile.hasRetraceResult()
+              ? retracedSourceFile.getSourceFile()
+              : RetraceUtils.inferSourceFile(
+                  classReference.getTypeName(), original.getSourceFile(), classResult.isEmpty()));
+    };
   }
 
-  private Stream<RetraceStackTraceElementProxyImpl<T, ST>> retraceFieldOrReturnType(
-      Stream<RetraceStackTraceElementProxyImpl<T, ST>> currentResults, ST element) {
+  private RetraceStackTraceElementProxyResultImpl<T, ST> retraceFieldOrReturnType(
+      RetraceStackTraceElementProxyResultImpl<T, ST> currentResult, ST element) {
     if (!element.hasFieldOrReturnType()) {
-      return currentResults;
+      return currentResult;
     }
+    RetraceStackTraceElementProxyResultImpl.Builder<T, ST> resultBuilder = currentResult.builder();
     String elementOrReturnType = element.getFieldOrReturnType();
     if (elementOrReturnType.equals("void")) {
-      return currentResults.map(
-          proxy ->
-              proxy
-                  .builder()
-                  .setRetracedFieldOrReturnType(RetracedTypeReferenceImpl.createVoid())
-                  .build());
+      return resultBuilder
+          .setResultStream(
+              currentResult.stream()
+                  .map(
+                      proxy ->
+                          buildProxyForRewrittenReturnType(
+                              proxy, RetracedTypeReferenceImpl.createVoid(), proxy.isAmbiguous())))
+          .build();
     } else {
       TypeReference typeReference = Reference.typeFromTypeName(elementOrReturnType);
       RetraceTypeResult retraceTypeResult = retracer.retraceType(typeReference);
       List<Element> retracedElements = retraceTypeResult.stream().collect(Collectors.toList());
-      return currentResults.flatMap(
-          proxy ->
-              retracedElements.stream()
-                  .map(
-                      retracedResult ->
-                          proxy
-                              .builder()
-                              .setRetracedFieldOrReturnType(retracedResult.getType())
-                              .joinAmbiguous(retraceTypeResult.isAmbiguous())
-                              .build()));
+      return resultBuilder
+          .setResultStream(
+              currentResult.stream()
+                  .flatMap(
+                      proxy ->
+                          retracedElements.stream()
+                              .map(
+                                  retracedResult ->
+                                      buildProxyForRewrittenReturnType(
+                                          proxy,
+                                          retracedResult.getType(),
+                                          retraceTypeResult.isAmbiguous()))))
+          .build();
     }
   }
 
-  private Stream<RetraceStackTraceElementProxyImpl<T, ST>> retracedMethodArguments(
-      Stream<RetraceStackTraceElementProxyImpl<T, ST>> currentResults, ST element) {
+  private RetraceStackTraceElementProxyImpl<T, ST> buildProxyForRewrittenReturnType(
+      RetraceStackTraceElementProxyImpl<T, ST> proxy,
+      RetracedTypeReference type,
+      boolean isAmbiguous) {
+    return proxy.builder().setRetracedFieldOrReturnType(type).joinAmbiguous(isAmbiguous).build();
+  }
+
+  private RetraceStackTraceElementProxyResultImpl<T, ST> retracedMethodArguments(
+      RetraceStackTraceElementProxyResultImpl<T, ST> currentResult, ST element) {
     if (!element.hasMethodArguments()) {
-      return currentResults;
+      return currentResult;
     }
     List<RetraceTypeResult> retracedResults =
         Arrays.stream(element.getMethodArguments().split(","))
@@ -243,19 +324,24 @@
               return newResult;
             });
     boolean isAmbiguous = allRetracedArguments.size() > 1;
-    return currentResults.flatMap(
-        proxy ->
-            allRetracedArguments.stream()
-                .map(
-                    retracedArguments ->
-                        proxy
-                            .builder()
-                            .setRetracedMethodArguments(retracedArguments)
-                            .joinAmbiguous(isAmbiguous)
-                            .build()));
+    return currentResult
+        .builder()
+        .setResultStream(
+            currentResult.stream()
+                .flatMap(
+                    proxy ->
+                        allRetracedArguments.stream()
+                            .map(
+                                retracedArguments ->
+                                    proxy
+                                        .builder()
+                                        .setRetracedMethodArguments(retracedArguments)
+                                        .joinAmbiguous(isAmbiguous)
+                                        .build())))
+        .build();
   }
 
-  public static class RetraceStackTraceElementProxyImpl<T, ST extends StackTraceElementProxy<T, ST>>
+  static class RetraceStackTraceElementProxyImpl<T, ST extends StackTraceElementProxy<T, ST>>
       implements RetraceStackTraceElementProxy<T, ST> {
 
     private final ST originalItem;
@@ -383,7 +469,7 @@
           originalItem, null, null, null, null, null, null, -1, false, false, context);
     }
 
-    private Builder<T, ST> builder() {
+    Builder<T, ST> builder() {
       Builder<T, ST> builder = new Builder<>(originalItem);
       builder.classContext = retracedClass;
       builder.methodContext = retracedMethod;
@@ -522,6 +608,11 @@
         return this;
       }
 
+      private Builder<T, ST> apply(Consumer<Builder<T, ST>> consumer) {
+        consumer.accept(this);
+        return this;
+      }
+
       private Builder<T, ST> applyIf(boolean condition, Consumer<Builder<T, ST>> consumer) {
         if (condition) {
           consumer.accept(this);
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
index fc195d0..b01f4ea 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
@@ -237,10 +237,16 @@
           new StringIndex(
               startIndex,
               endIndex,
-              (retraced, original, verbose) ->
-                  (retraced.hasLineNumber()
-                      ? ((insertSeparatorForRetraced ? ":" : "") + retraced.getLineNumber())
-                      : original.lineNumberAsString()));
+              (retraced, original, verbose) -> {
+                boolean printLineNumber =
+                    retraced.hasLineNumber()
+                        && ((original.hasLineNumber() && original.getLineNumber() > -1)
+                            || !retraced.isAmbiguous()
+                            || verbose);
+                return printLineNumber
+                    ? ((insertSeparatorForRetraced ? ":" : "") + retraced.getLineNumber())
+                    : original.lineNumberAsString();
+              });
       orderedIndices.add(lineNumber);
       return this;
     }
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 b5235d2..9449f1b 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1203,6 +1203,9 @@
     // See b/191469661 for why this is here.
     public boolean noCfMarkerForDesugaredCode =
         System.getProperty("com.android.tools.r8.noCfMarkerForDesugaredCode") != null;
+    // See b/182065081 for why this is here.
+    public boolean lambdaClassFieldsFinal =
+        System.getProperty("com.android.tools.r8.lambdaClassFieldsNotFinal") == null;
   }
 
   public class CallSiteOptimizationOptions {
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 48af35f..44ab3fc 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -106,8 +106,11 @@
       }
 
       Position newPosition =
-          new Position(nextOptimizedLineNumber, position.file, position.method, null);
-      ++nextOptimizedLineNumber;
+          position
+              .builderWithCopy()
+              .setLine(nextOptimizedLineNumber++)
+              .setCallerPosition(null)
+              .build();
       previousSourceLine = position.line;
       previousMethod = position.method;
       return new Pair<>(position, newPosition);
@@ -180,14 +183,17 @@
           if (calleePosition != null) {
             // Take the first line as the callee position
             position =
-                new Position(
-                    calleePosition.getValue().getRange().from,
-                    position.file,
-                    position.method,
-                    position.callerPosition);
+                position
+                    .builderWithCopy()
+                    .setLine(calleePosition.getValue().getRange().from)
+                    .build();
           }
           return baseRemapper.createRemappedPosition(
-              new Position(originalInlineeLine, null, inlinee, position));
+              Position.builder()
+                  .setLine(originalInlineeLine)
+                  .setMethod(inlinee)
+                  .setCallerPosition(position)
+                  .build());
         }
         // This is the same position, so we should really not mark this as an inline position. Fall
         // through to the default case.
@@ -244,7 +250,8 @@
     private void emitPositionEvents(int currentPc, Position currentPosition) {
       if (previousPosition == null) {
         startLine = currentPosition.line;
-        previousPosition = new Position(startLine, null, method, null);
+        previousPosition =
+            Position.builder().setLine(startLine).setFile(null).setMethod(method).build();
       }
       DexDebugEventBuilder.emitAdvancementEvents(
           previousPc,
@@ -702,11 +709,12 @@
             super.visit(defaultEvent);
             assert getCurrentLine() >= 0;
             Position position =
-                new Position(
-                    getCurrentLine(),
-                    getCurrentFile(),
-                    getCurrentMethod(),
-                    getCurrentCallerPosition());
+                Position.builder()
+                    .setLine(getCurrentLine())
+                    .setFile(getCurrentFile())
+                    .setMethod(getCurrentMethod())
+                    .setCallerPosition(getCurrentCallerPosition())
+                    .build();
             Position currentPosition = remapAndAdd(position, positionRemapper, mappedPositions);
             positionEventEmitter.emitPositionEvents(getCurrentPc(), currentPosition);
             if (currentPosition != position) {
@@ -801,11 +809,12 @@
             }
             lastPosition.setFirst(getCurrentPc());
             lastPosition.setSecond(
-                new Position(
-                    getCurrentLine(),
-                    getCurrentFile(),
-                    getCurrentMethod(),
-                    getCurrentCallerPosition()));
+                Position.builder()
+                    .setLine(getCurrentLine())
+                    .setFile(getCurrentFile())
+                    .setMethod(getCurrentMethod())
+                    .setCallerPosition(getCurrentCallerPosition())
+                    .build());
           }
         };
 
diff --git a/src/main/java/com/android/tools/r8/utils/OptionalUtils.java b/src/main/java/com/android/tools/r8/utils/OptionalUtils.java
new file mode 100644
index 0000000..974bf0c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/OptionalUtils.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import java.util.OptionalInt;
+import java.util.function.Supplier;
+
+public class OptionalUtils {
+
+  public static OptionalInt orElse(OptionalInt optional, int orElse) {
+    return optional.isPresent() ? optional : OptionalInt.of(orElse);
+  }
+
+  public static OptionalInt orElseGet(OptionalInt optional, Supplier<Integer> orElse) {
+    return optional.isPresent() ? optional : OptionalInt.of(orElse.get());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/Timing.java b/src/main/java/com/android/tools/r8/utils/Timing.java
index 5652476..c1046e9 100644
--- a/src/main/java/com/android/tools/r8/utils/Timing.java
+++ b/src/main/java/com/android/tools/r8/utils/Timing.java
@@ -21,6 +21,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Stack;
+import java.util.function.Supplier;
 
 public class Timing {
 
@@ -370,6 +371,15 @@
     stack.push(child);
   }
 
+  public <T> T time(String title, Supplier<T> supplier) {
+    begin(title);
+    try {
+      return supplier.get();
+    } finally {
+      end();
+    }
+  }
+
   public void end() {
     stack.peek().end();  // record time.
     stack.pop();
diff --git a/src/main/keep_retrace.txt b/src/main/keep_retrace.txt
deleted file mode 100644
index 6a71f9e..0000000
--- a/src/main/keep_retrace.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-# for details. All rights reserved. Use of this source code is governed by a
-# BSD-style license that can be found in the LICENSE file.
-
-# The retrace api is separated out without repackaging which is why this broad
-# rule is used.
--keep public class com.android.tools.r8.retrace.* {
-     public <methods>;
-     public <fields>;
- }
--keepattributes SourceFile, LineNumberTable, InnerClasses, EnclosingMethod, Exceptions, Signature
--keepparameternames
-# This is run on r8lib so keep everything in lib that is traced. That way
-# we only need a single mapping file
--keep,allowshrinking class * { *; }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/Collectors.java b/src/test/java/com/android/tools/r8/CollectorsUtils.java
similarity index 92%
rename from src/test/java/com/android/tools/r8/Collectors.java
rename to src/test/java/com/android/tools/r8/CollectorsUtils.java
index 0e05ce0..93d77b0 100644
--- a/src/test/java/com/android/tools/r8/Collectors.java
+++ b/src/test/java/com/android/tools/r8/CollectorsUtils.java
@@ -6,7 +6,7 @@
 
 import java.util.stream.Collector;
 
-public abstract class Collectors {
+public abstract class CollectorsUtils {
 
   public static <T> Collector<T, ?, T> toSingle() {
     return java.util.stream.Collectors.collectingAndThen(
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index be6c8f0..56a2b80 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -176,7 +176,6 @@
   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_all.jar");
-  public static final Path R8_RETRACE_JAR = Paths.get(LIBS_DIR, "r8retrace.jar");
 
   public static final Path DESUGAR_LIB_CONVERSIONS =
       Paths.get(LIBS_DIR, "library_desugar_conversions.zip");
@@ -2127,12 +2126,6 @@
     return builder;
   }
 
-  public static R8Command.Builder allowPartiallyImplementedProguardOptions(
-      R8Command.Builder builder) {
-    builder.allowPartiallyImplementedProguardOptions();
-    return builder;
-  }
-
   public static R8Command.Builder allowTestProguardOptions(R8Command.Builder builder) {
     builder.allowTestProguardOptions();
     return builder;
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
index 1a6717f..d1dc197 100644
--- a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
@@ -10,6 +10,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.ExternalR8TestCompileResult;
@@ -154,9 +155,7 @@
     // Produce r81 = R8Lib(R8WithDeps) and r82 = R8LibNoDeps + Deps(R8WithDeps) and test that r81 is
     // equal to r82. This test should only run if we are testing r8lib and we expect both R8libs to
     // be built by gradle. If we are not testing with R8Lib, do not run this test.
-    if (!ToolHelper.isTestingR8Lib()) {
-      return;
-    }
+    assumeTrue(ToolHelper.isTestingR8Lib());
     Path runR81 =
         testForExternalR8(parameters.getBackend(), parameters.getRuntime())
             .useProvidedR8(ToolHelper.R8LIB_JAR)
diff --git a/src/test/java/com/android/tools/r8/compilerapi/BinaryCompatibilityTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/BinaryCompatibilityTestCollection.java
new file mode 100644
index 0000000..51a307b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/BinaryCompatibilityTestCollection.java
@@ -0,0 +1,211 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.compilerapi;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CollectorsUtils;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.transformers.ClassFileTransformer.InnerClassPredicate;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import java.io.File;
+import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Abstract base to define a collection of API tests that should be run against a checked in jar.
+ */
+public abstract class BinaryCompatibilityTestCollection<T> {
+
+  private static final String JUNIT_JAR = "junit-4.13-beta-2.jar";
+
+  private static final String HAMCREST = "hamcrest-core-1.3.jar";
+
+  /** Jar to run tests against. */
+  public abstract Path getTargetJar();
+
+  /** Jar with tests. */
+  public abstract Path getCheckedInTestJar();
+
+  /** List of classes that make up the checked in test suite. */
+  public abstract List<Class<? extends T>> getCheckedInTestClasses();
+
+  /** List of classes that are yet to become part of the test suite. */
+  public abstract List<Class<? extends T>> getPendingTestClasses();
+
+  /** Additional classes that should always be included together to run tests. */
+  public abstract List<Class<?>> getAdditionalClassesForTests();
+
+  /** Additional JVM args supplied to any external execution. */
+  public abstract List<String> getVmArgs();
+
+  /** Temporary folder for generating jars and so on. */
+  public abstract TemporaryFolder getTemp();
+
+  public String makeProperty(String key, String value) {
+    return "-D" + key + "=" + value;
+  }
+
+  private void verifyConsistency() {
+    assertEquals(
+        ImmutableSet.of(),
+        Sets.intersection(
+            ImmutableSet.copyOf(getCheckedInTestClasses()),
+            ImmutableSet.copyOf(getPendingTestClasses())));
+  }
+
+  private boolean testIsCheckedInOrPending(Class<?> clazz) {
+    return getCheckedInTestClasses().contains(clazz) || getPendingTestClasses().contains(clazz);
+  }
+
+  public void runJunitOnCheckedInJar() throws Exception {
+    runJunitOnTestClasses(getCheckedInTestJar(), getCheckedInTestClasses());
+  }
+
+  public void runJunitOnTestClass(Class<? extends T> test) throws Exception {
+    List<Class<? extends T>> testClasses = Collections.singletonList(test);
+    runJunitOnTestClasses(generateJarForTestClasses(testClasses), testClasses);
+  }
+
+  private void runJunitOnTestClasses(Path testJar, Collection<Class<? extends T>> tests)
+      throws Exception {
+    verifyConsistency();
+    IntBox numberOfTestMethods = new IntBox(0);
+    List<Path> classPaths =
+        ImmutableList.of(getJunitDependency(), getHamcrest(), getTargetJar(), testJar);
+    List<String> args = new ArrayList<>();
+    args.add("org.junit.runner.JUnitCore");
+    tests.forEach(
+        test -> {
+          assertTrue(testIsCheckedInOrPending(test));
+          args.add(test.getTypeName());
+          for (Method method : test.getDeclaredMethods()) {
+            if (method.isAnnotationPresent(Test.class)) {
+              numberOfTestMethods.increment();
+            }
+          }
+        });
+    ProcessResult processResult =
+        ToolHelper.runJava(
+            TestRuntime.getSystemRuntime(),
+            ImmutableList.<String>builder().add("-ea").addAll(getVmArgs()).build(),
+            classPaths,
+            args.toArray(new String[0]));
+    assertEquals(processResult.toString(), 0, processResult.exitCode);
+    assertThat(processResult.stdout, containsString("OK (" + numberOfTestMethods.get() + " test"));
+  }
+
+  private static Path getJunitDependency() {
+    String junitPath =
+        Arrays.stream(System.getProperty("java.class.path").split(File.pathSeparator))
+            .filter(cp -> cp.endsWith(JUNIT_JAR))
+            .collect(CollectorsUtils.toSingle());
+    return Paths.get(junitPath);
+  }
+
+  private static Path getHamcrest() {
+    String junitPath =
+        Arrays.stream(System.getProperty("java.class.path").split(File.pathSeparator))
+            .filter(cp -> cp.endsWith(HAMCREST))
+            .collect(CollectorsUtils.toSingle());
+    return Paths.get(junitPath);
+  }
+
+  public Path generateJarForCheckedInTestClasses() throws Exception {
+    return generateJarForTestClasses(getCheckedInTestClasses());
+  }
+
+  private Path generateJarForTestClasses(Collection<Class<? extends T>> classes) throws Exception {
+    Path jar = getTemp().newFolder().toPath().resolve("test.jar");
+    ZipBuilder zipBuilder = ZipBuilder.builder(jar);
+    for (Class<? extends T> test : classes) {
+      zipBuilder.addFilesRelative(
+          ToolHelper.getClassPathForTests(), ToolHelper.getClassFilesForInnerClasses(test));
+      zipBuilder.addBytes(
+          ZipUtils.zipEntryNameForClass(test),
+          ClassFileTransformer.create(test)
+              .removeInnerClasses(
+                  InnerClassPredicate.onName(
+                      DescriptorUtils.getBinaryNameFromJavaType(test.getTypeName())))
+              .transform());
+    }
+    zipBuilder.addFilesRelative(
+        ToolHelper.getClassPathForTests(),
+        getAdditionalClassesForTests().stream()
+            .map(ToolHelper::getClassFileForTestClass)
+            .collect(Collectors.toList()));
+    return zipBuilder.build();
+  }
+
+  public void verifyCheckedInJarIsUpToDate() throws Exception {
+    TemporaryFolder temp = getTemp();
+    Path checkedInContents = temp.newFolder().toPath();
+    Path generatedContents = temp.newFolder().toPath();
+    ZipUtils.unzip(getCheckedInTestJar(), checkedInContents);
+    ZipUtils.unzip(generateJarForCheckedInTestClasses(), generatedContents);
+    try (Stream<Path> existingPaths = Files.walk(checkedInContents);
+        Stream<Path> generatedPaths = Files.walk(generatedContents)) {
+      List<Path> existing =
+          existingPaths.filter(FileUtils::isClassFile).collect(Collectors.toList());
+      List<Path> generated =
+          generatedPaths.filter(FileUtils::isClassFile).collect(Collectors.toList());
+      for (Path classFile : generated) {
+        Path otherClassFile = checkedInContents.resolve(generatedContents.relativize(classFile));
+        assertTrue("Could not find file: " + otherClassFile, Files.exists(otherClassFile));
+        assertTrue(
+            "Non-equal files: " + otherClassFile,
+            TestBase.filesAreEqual(classFile, otherClassFile));
+      }
+      assertEquals(existing.size(), generated.size());
+      assertNotEquals(0, existing.size());
+    }
+  }
+
+  public void replaceJarForCheckedInTestClasses() throws Exception {
+    Path checkedInJar = getCheckedInTestJar();
+    Path tarballDir = checkedInJar.getParent();
+    Path parentDir = tarballDir.getParent();
+    if (!Files.exists(Paths.get(tarballDir + ".tar.gz.sha1"))) {
+      throw new RuntimeException("Could not locate the SHA file for " + tarballDir);
+    }
+    Path generatedJar = generateJarForCheckedInTestClasses();
+    Files.move(generatedJar, checkedInJar, StandardCopyOption.REPLACE_EXISTING);
+    System.out.println(
+        "Updated file in: "
+            + checkedInJar
+            + "\nRemember to upload to cloud storage:"
+            + "\n(cd "
+            + parentDir
+            + " && upload_to_google_storage.py -a --bucket r8-deps "
+            + tarballDir.getFileName()
+            + ")");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java
new file mode 100644
index 0000000..654eafd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTest.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.compilerapi;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Collections;
+import java.util.List;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Base class for any actual API test.
+ *
+ * <p>Subclasses of this must only use the public API and the otherwise linked libraries (junit,
+ * etc).
+ */
+@RunWith(Parameterized.class)
+public abstract class CompilerApiTest {
+
+  public static final String API_TEST_MODE_KEY = "API_TEST_MODE";
+  public static final String API_TEST_MODE_EXTERNAL = "external";
+
+  public static final String API_TEST_LIB_KEY = "API_TEST_LIB";
+  public static final String API_TEST_LIB_YES = "yes";
+  public static final String API_TEST_LIB_NO = "no";
+
+  @Parameters(name = "{0}")
+  public static List<Object> data() {
+    // Simulate only running the API tests directly on the "none" runtime configuration.
+    String runtimes = System.getProperty("runtimes");
+    if (runtimes != null && !runtimes.contains("none")) {
+      return Collections.emptyList();
+    }
+    return Collections.singletonList("none");
+  }
+
+  public CompilerApiTest(Object none) {
+    assertEquals("none", none);
+  }
+
+  /** Predicate to determine if the test is being run externally. */
+  public boolean isRunningExternal() {
+    return API_TEST_MODE_EXTERNAL.equals(System.getProperty(API_TEST_MODE_KEY));
+  }
+
+  /** Predicate to determine if the test is being run for an R8 lib compilation. */
+  public boolean isRunningR8Lib() {
+    return API_TEST_LIB_YES.equals(System.getProperty(API_TEST_LIB_KEY));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
new file mode 100644
index 0000000..8c1e8a5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.compilerapi;
+
+import static com.android.tools.r8.ToolHelper.R8LIB_JAR;
+import static com.android.tools.r8.ToolHelper.R8_JAR;
+import static com.android.tools.r8.ToolHelper.isTestingR8Lib;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.compilerapi.testsetup.ApiTestingSetUpTest;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.rules.TemporaryFolder;
+
+/** Collection of API tests for the D8/R8 compilers. */
+public class CompilerApiTestCollection extends BinaryCompatibilityTestCollection<CompilerApiTest> {
+
+  private static final String DIRNAME = "compiler_api_tests";
+  private static final Path BINARY_COMPATIBILITY_JAR =
+      Paths.get(ToolHelper.THIRD_PARTY_DIR, "binary_compatibility_tests", DIRNAME, "tests.jar");
+
+  private static final List<Class<? extends CompilerApiTest>> CLASSES_FOR_BINARY_COMPATIBILITY =
+      ImmutableList.of(ApiTestingSetUpTest.ApiTest.class);
+
+  private static final List<Class<? extends CompilerApiTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
+      ImmutableList.of(
+          // No pending APIs.
+          );
+
+  private final TemporaryFolder temp;
+
+  public CompilerApiTestCollection(TemporaryFolder temp) {
+    this.temp = temp;
+  }
+
+  @Override
+  public TemporaryFolder getTemp() {
+    return temp;
+  }
+
+  @Override
+  public List<Class<? extends CompilerApiTest>> getCheckedInTestClasses() {
+    return CLASSES_FOR_BINARY_COMPATIBILITY;
+  }
+
+  @Override
+  public List<Class<? extends CompilerApiTest>> getPendingTestClasses() {
+    return CLASSES_PENDING_BINARY_COMPATIBILITY;
+  }
+
+  @Override
+  public List<Class<?>> getAdditionalClassesForTests() {
+    return ImmutableList.of(CompilerApiTest.class);
+  }
+
+  @Override
+  public Path getCheckedInTestJar() {
+    return BINARY_COMPATIBILITY_JAR;
+  }
+
+  // The API tests always link against the jar that the test runner is using.
+  public Path getTargetJar() {
+    return isTestingR8Lib() ? R8LIB_JAR : R8_JAR;
+  }
+
+  // Some tests expectations can depend on the lib/nonlib and internal/external behavior.
+  // This sets up envvars so the test can determine its running context.
+  // This is only called for external invocations.
+  public List<String> getVmArgs() {
+    return ImmutableList.of(
+        makeProperty("com.android.tools.r8.enableTestAssertions", "1"),
+        makeProperty(CompilerApiTest.API_TEST_MODE_KEY, CompilerApiTest.API_TEST_MODE_EXTERNAL),
+        makeProperty(
+            CompilerApiTest.API_TEST_LIB_KEY,
+            isTestingR8Lib() ? CompilerApiTest.API_TEST_LIB_YES : CompilerApiTest.API_TEST_LIB_NO));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollectionTest.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollectionTest.java
new file mode 100644
index 0000000..0aae1dd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollectionTest.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.compilerapi;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class CompilerApiTestCollectionTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public CompilerApiTestCollectionTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  /**
+   * If this test fails the API has changed in a non-compatible way. Likely the changes to the API
+   * will need to be undone/changed to preserve compatibility.
+   */
+  @Test
+  public void runCheckedInTests() throws Exception {
+    new CompilerApiTestCollection(temp).runJunitOnCheckedInJar();
+  }
+
+  /**
+   * If this test fails the test.jar needs to be regenerated and uploaded to cloud storage.
+   *
+   * <p>See: {@code CompilerApiTestCollection.main} to regenerate.
+   *
+   * <p>To preserve compatibility, make sure only to regenerate together with test changes and with
+   * NO changes to the compiler itself.
+   */
+  @Test
+  public void testCheckedInJarIsUpToDate() throws Exception {
+    new CompilerApiTestCollection(temp).verifyCheckedInJarIsUpToDate();
+  }
+
+  /**
+   * To produce a new tests.jar run the code below. This will generate a new jar overwriting the
+   * existing one. Remember to upload to cloud storage afterwards.
+   */
+  public static void main(String[] args) throws Exception {
+    TemporaryFolder temp = new TemporaryFolder();
+    temp.create();
+    new CompilerApiTestCollection(temp).replaceJarForCheckedInTestClasses();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestRunner.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestRunner.java
new file mode 100644
index 0000000..ee135e8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestRunner.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.compilerapi;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Base runner for all compiler API tests.
+ *
+ * <p>Using this runner will automatically create an externalized variant of the test. That is
+ * useful to more quickely ensure the test itself is not using resources that are not available.
+ * Note however, that it does not prevent using non-kept code in the compilers unless testing with
+ * r8lib!
+ */
+@RunWith(Parameterized.class)
+public abstract class CompilerApiTestRunner extends TestBase {
+
+  public abstract Class<? extends CompilerApiTest> binaryTestClass();
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public CompilerApiTestRunner(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  @Test
+  public void testExternal() throws Exception {
+    new CompilerApiTestCollection(temp).runJunitOnTestClass(binaryTestClass());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/compilerapi/testsetup/ApiTestingSetUpTest.java b/src/test/java/com/android/tools/r8/compilerapi/testsetup/ApiTestingSetUpTest.java
new file mode 100644
index 0000000..7cf5b65
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/compilerapi/testsetup/ApiTestingSetUpTest.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.compilerapi.testsetup;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.compilerapi.CompilerApiTest;
+import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.utils.InternalOptions;
+import org.junit.Test;
+
+public class ApiTestingSetUpTest extends CompilerApiTestRunner {
+
+  public ApiTestingSetUpTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<? extends CompilerApiTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  public static class ApiTest extends CompilerApiTest {
+
+    public ApiTest(Object parameters) {
+      super(parameters);
+    }
+
+    @Test
+    public void testValidApiUse() throws Exception {
+      R8Command.builder().setPrintHelp(true).build();
+    }
+
+    @Test
+    public void testNonExistingApiUse() throws Exception {
+      try {
+        new InternalOptions();
+        // When running directly the class is public and visible.
+        assertFalse(isRunningR8Lib());
+      } catch (NoClassDefFoundError e) {
+        // Internal options is not kept, so this access should fail externally.
+        assertTrue(isRunningR8Lib());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/ArgumentLocalsInLoopTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/ArgumentLocalsInLoopTestRunner.java
index 250bf86..b4de89d 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/ArgumentLocalsInLoopTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/ArgumentLocalsInLoopTestRunner.java
@@ -14,15 +14,12 @@
   public void testArgumentLocalsInLoop() throws Exception {
     Class clazz = ArgumentLocalsInLoopTest.class;
     AndroidApp d8App = compileWithD8(clazz);
-    AndroidApp dxApp = getDxCompiledSources();
 
     String expected = "0";
     assertEquals(expected, runOnJava(clazz));
     assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
-    assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
 
     checkFoo(inspectMethod(d8App, clazz, "int", "foo", "int"), clazz);
-    checkFoo(inspectMethod(dxApp, clazz, "int", "foo", "int"), clazz);
   }
 
   private void checkFoo(DebugInfoInspector info, Class clazz) {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/BackBranchToSelfTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/BackBranchToSelfTestRunner.java
index 0ad8ecd..6b84390 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/BackBranchToSelfTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/BackBranchToSelfTestRunner.java
@@ -15,15 +15,12 @@
     Class clazz = BackBranchToSelfTest.class;
 
     AndroidApp d8App = compileWithD8(clazz);
-    AndroidApp dxApp = getDxCompiledSources();
 
     String expected = "42";
     assertEquals(expected, runOnJava(clazz));
     assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
-    assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
 
     checkBackBranchToSelf(inspectMethod(d8App, clazz, "int", "backBranchToSelf", "boolean"));
-    checkBackBranchToSelf(inspectMethod(dxApp, clazz, "int", "backBranchToSelf", "boolean"));
   }
 
   private void checkBackBranchToSelf(DebugInfoInspector info) {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/CodeGeneratorTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/CodeGeneratorTestRunner.java
index 062a4d3..1f6520e 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/CodeGeneratorTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/CodeGeneratorTestRunner.java
@@ -24,12 +24,10 @@
     Class clazz = CodeGeneratorTest.class;
 
     AndroidApp d8App = compileWithD8(clazz);
-    AndroidApp dxApp = getDxCompiledSources();
 
     String expected = "11";
     assertEquals(expected, runOnJava(clazz));
     assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
-    assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
 
     DebugInfoInspector inspector = inspectMethod(d8App, clazz, "int", "intAddition", "int", "int",
         "int");
diff --git a/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTestRunner.java
index 8348c0b..07fd195 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/ConditionalLocalTestRunner.java
@@ -15,15 +15,12 @@
     Class clazz = ConditionalLocalTest.class;
 
     AndroidApp d8App = compileWithD8(clazz);
-    AndroidApp dxApp = getDxCompiledSources();
 
     String expected = "42";
     assertEquals(expected, runOnJava(clazz));
     assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
-    assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
 
     checkConditonalLocal(inspectMethod(d8App, clazz, "void", "foo", "int"));
-    checkConditonalLocal(inspectMethod(dxApp, clazz, "void", "foo", "int"));
   }
 
   private void checkConditonalLocal(DebugInfoInspector info) {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/ConstantFoldingTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/ConstantFoldingTestRunner.java
index a229add..6184b2d 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/ConstantFoldingTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/ConstantFoldingTestRunner.java
@@ -14,18 +14,15 @@
   public void testLocalsInSwitch() throws Exception {
     Class clazz = ConstantFoldingTest.class;
     AndroidApp d8App = compileWithD8(clazz);
-    AndroidApp dxApp = getDxCompiledSources();
 
     String expected = "42";
     assertEquals(expected, runOnJava(clazz));
     assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
-    assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
 
-    checkFoo(inspectMethod(d8App, clazz, "int", "foo", "int"), false);
-    checkFoo(inspectMethod(dxApp, clazz, "int", "foo", "int"), true);
+    checkFoo(inspectMethod(d8App, clazz, "int", "foo", "int"));
   }
 
-  private void checkFoo(DebugInfoInspector info, boolean dx) {
+  private void checkFoo(DebugInfoInspector info) {
     info.checkStartLine(9);
     info.checkLineHasExactLocals(9, "x", "int");
     info.checkNoLine(10);
@@ -33,10 +30,7 @@
     info.checkLineHasExactLocals(12, "x", "int", "res", "int", "tmp", "int");
     info.checkNoLine(13);
     info.checkLineHasAtLeastLocals(14, "x", "int");
-    if (!dx) {
-      // DX fails to close the scope of "tmp".
-      info.checkLineHasExactLocals(14, "x", "int", "res", "int");
-    }
+    info.checkLineHasExactLocals(14, "x", "int", "res", "int");
     info.checkNoLine(15);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoTestBase.java b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoTestBase.java
index da83ed9..09df151 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoTestBase.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoTestBase.java
@@ -26,9 +26,6 @@
 
 public class DebugInfoTestBase {
 
-  public static final Path DX_PREBUILT =
-      Paths.get(ToolHelper.BUILD_DIR, "test", "debuginfo_examples_dex.jar");
-
   @Rule
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
@@ -45,10 +42,6 @@
     return appSink.build();
   }
 
-  static AndroidApp getDxCompiledSources() throws IOException {
-    return AndroidApp.builder().addProgramFiles(DX_PREBUILT).build();
-  }
-
   public static DebugInfoInspector inspectMethod(
       AndroidApp app, Class type, String returnType, String methodName, String... parameterTypes)
       throws IOException, ExecutionException {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
index d6e30b4..37d2b6e 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DexPcWithDebugInfoForOverloadedMethodsTestRunner.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.debuginfo;
 
-import static com.android.tools.r8.Collectors.toSingle;
+import static com.android.tools.r8.CollectorsUtils.toSingle;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
diff --git a/src/test/java/com/android/tools/r8/debuginfo/ExceptionLocalTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/ExceptionLocalTestRunner.java
index 2998c70..f56fb6e 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/ExceptionLocalTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/ExceptionLocalTestRunner.java
@@ -16,15 +16,12 @@
     Class clazz = ExceptionLocalTest.class;
 
     AndroidApp d8App = compileWithD8(clazz);
-    AndroidApp dxApp = getDxCompiledSources();
 
     String expected = "2";
     assertEquals(expected, runOnJava(clazz));
     assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
-    assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
 
     checkExceptionLocal(inspectMethod(d8App, clazz, "void", "foo", "int"));
-    checkExceptionLocal(inspectMethod(dxApp, clazz, "void", "foo", "int"));
   }
 
   private void checkExceptionLocal(DebugInfoInspector info) {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/LiveInAllBlocksTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/LiveInAllBlocksTestRunner.java
index 01bcb27..0e723f6 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/LiveInAllBlocksTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/LiveInAllBlocksTestRunner.java
@@ -15,24 +15,17 @@
   public void testLiveInAllBlocks() throws Exception {
     Class clazz = LiveInAllBlocksTest.class;
     AndroidApp d8App = compileWithD8(clazz);
-    AndroidApp dxApp = getDxCompiledSources();
 
     String expected = "42";
     assertEquals(expected, runOnJava(clazz));
     assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
-    assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
 
-    checkFoo(inspectMethod(d8App, clazz, "int", "foo", "int"), false);
-    checkFoo(inspectMethod(dxApp, clazz, "int", "foo", "int"), true);
+    checkFoo(inspectMethod(d8App, clazz, "int", "foo", "int"));
   }
 
-  private void checkFoo(DebugInfoInspector info, boolean dx) {
+  private void checkFoo(DebugInfoInspector info) {
     info.checkStartLine(9);
     for (int line : new int[] {14, 18, 23, 24, 25, 27, 28, 30, 31, 34, 35, 37, 38, 40, 41}) {
-      if (dx && line == 18) {
-        // DX does not keep entry for line 18.
-        continue;
-      }
       info.checkLineHasAtLeastLocals(line, "x", "int", "y", "int");
     }
   }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/LocalSwapTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/LocalSwapTestRunner.java
index 0ee33bf..12887fe 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/LocalSwapTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/LocalSwapTestRunner.java
@@ -14,27 +14,21 @@
   public void testLocalSwap() throws Exception {
     Class clazz = LocalSwapTest.class;
     AndroidApp d8App = compileWithD8(clazz);
-    AndroidApp dxApp = getDxCompiledSources();
 
     String expected = "6";
     assertEquals(expected, runOnJava(clazz));
     assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
-    assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
 
-    checkFoo(inspectMethod(d8App, clazz, "int", "foo", "int", "int"), false);
-    checkFoo(inspectMethod(dxApp, clazz, "int", "foo", "int", "int"), true);
+    checkFoo(inspectMethod(d8App, clazz, "int", "foo", "int", "int"));
   }
 
-  private void checkFoo(DebugInfoInspector info, boolean dx) {
+  private void checkFoo(DebugInfoInspector info) {
     info.checkStartLine(9);
     info.checkLineHasExactLocals(9, "x", "int", "y", "int");
     info.checkLineHasExactLocals(11, "x", "int", "y", "int", "sum", "int");
     info.checkLineHasExactLocals(12, "x", "int", "y", "int", "sum", "int", "t", "int");
     info.checkLineExists(13);
     info.checkLineExists(15);
-    if (!dx) {
-      // DX fails to close the scope of local "t".
-      info.checkLineHasExactLocals(15, "x", "int", "y", "int", "sum", "int");
-    }
+    info.checkLineHasExactLocals(15, "x", "int", "y", "int", "sum", "int");
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/LocalsAtThrowTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/LocalsAtThrowTestRunner.java
index 8a5b77d..4be9f50 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/LocalsAtThrowTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/LocalsAtThrowTestRunner.java
@@ -15,15 +15,12 @@
     Class clazz = LocalsAtThrowTest.class;
 
     AndroidApp d8App = compileWithD8(clazz);
-    AndroidApp dxApp = getDxCompiledSources();
 
     String expected = "3";
     assertEquals(expected, runOnJava(clazz));
     assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
-    assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
 
     checkLocalsAtThrow(inspectMethod(d8App, clazz, "int", "localsAtThrow", "int"));
-    checkLocalsAtThrow(inspectMethod(dxApp, clazz, "int", "localsAtThrow", "int"));
   }
 
   private void checkLocalsAtThrow(DebugInfoInspector info) {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/LocalsInSwitchTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/LocalsInSwitchTestRunner.java
index af726bf..87141f7 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/LocalsInSwitchTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/LocalsInSwitchTestRunner.java
@@ -16,22 +16,17 @@
     Class clazz = LocalsInSwitchTest.class;
 
     AndroidApp d8App = compileWithD8(clazz);
-    AndroidApp dxApp = getDxCompiledSources();
 
     String expected = "55" + ToolHelper.LINE_SEPARATOR + "1862" + ToolHelper.LINE_SEPARATOR
             + "15130" + ToolHelper.LINE_SEPARATOR;
     assertEquals(expected, runOnJava(clazz));
     assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
-    assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
 
     checkNoLocals(inspectMethod(d8App, clazz, "int", "noLocals", "int"));
-    checkNoLocals(inspectMethod(dxApp, clazz, "int", "noLocals", "int"));
 
-    checkTempInCase(inspectMethod(d8App, clazz, "int", "tempInCase", "int"), false);
-    checkTempInCase(inspectMethod(dxApp, clazz, "int", "tempInCase", "int"), true);
+    checkTempInCase(inspectMethod(d8App, clazz, "int", "tempInCase", "int"));
 
     checkInitInCases(inspectMethod(d8App, clazz, "int", "initInCases", "int"));
-    checkInitInCases(inspectMethod(dxApp, clazz, "int", "initInCases", "int"));
   }
 
   private void checkNoLocals(DebugInfoInspector info) {
@@ -42,7 +37,7 @@
     info.checkLineHasExactLocals(15, "x", "int");
   }
 
-  private void checkTempInCase(DebugInfoInspector tempInCase, boolean dx) {
+  private void checkTempInCase(DebugInfoInspector tempInCase) {
     // int res =
     tempInCase.checkStartLine(20);
     tempInCase.checkLineHasExactLocals(20, "x", "int");
@@ -52,17 +47,11 @@
     //   int rem =
     tempInCase.checkLineHasExactLocals(22, "x", "int", "res", "int", "i", "int");
     //   switch (rem) {
-    if (!dx) {
-      // DX contains several entries for 23, one of which does not define 'rem'. Go figure...
-      tempInCase.checkLineHasExactLocals(23, "x", "int", "res", "int", "i", "int", "rem", "int");
-    }
+    tempInCase.checkLineHasExactLocals(23, "x", "int", "res", "int", "i", "int", "rem", "int");
     //   case 0:
     tempInCase.checkNoLine(24);
     //     return res
-    if (!dx) {
-      // DX does not produce a position at the return statement. Good stuff.
-      tempInCase.checkLineHasExactLocals(25, "x", "int", "res", "int", "i", "int", "rem", "int");
-    }
+    tempInCase.checkLineHasExactLocals(25, "x", "int", "res", "int", "i", "int", "rem", "int");
     //   case 5:
     tempInCase.checkNoLine(26);
     //     int tmp =
@@ -90,14 +79,9 @@
     // }
     tempInCase.checkNoLine(37);
     // res *= x;
-    if (!dx) {
-      // DX fails to end the scope of "i" after the loop.
-      tempInCase.checkLineHasExactLocals(38, "x", "int", "res", "int");
-    }
+    tempInCase.checkLineHasExactLocals(38, "x", "int", "res", "int");
     // return res;
-    if (!dx) {
-      tempInCase.checkLineHasExactLocals(39, "x", "int", "res", "int");
-    }
+    tempInCase.checkLineHasExactLocals(39, "x", "int", "res", "int");
   }
 
   private void checkInitInCases(DebugInfoInspector info) {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/LocalsWithTypeParamsRunner.java b/src/test/java/com/android/tools/r8/debuginfo/LocalsWithTypeParamsRunner.java
index 2dd50ed..09a53ee 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/LocalsWithTypeParamsRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/LocalsWithTypeParamsRunner.java
@@ -22,15 +22,12 @@
   @Test
   public void testLocalsWithTypeParams() throws Exception {
     AndroidApp d8App = compileWithD8(clazzMain, clazzA, clazzB);
-    AndroidApp dxApp = getDxCompiledSources();
 
     String expected = "42";
     assertEquals(expected, runOnJava(clazzMain));
     assertEquals(expected, runOnArt(d8App, nameMain));
-    assertEquals(expected, runOnArt(dxApp, nameMain));
 
     checkSyncInstance(inspectMethod(d8App, clazzA, "int", "foo", nameB));
-    checkSyncInstance(inspectMethod(dxApp, clazzA, "int", "foo", nameB));
   }
 
   private void checkSyncInstance(DebugInfoInspector info) {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/ScopedExceptionsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/ScopedExceptionsTestRunner.java
index 436334a..dbf9243 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/ScopedExceptionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/ScopedExceptionsTestRunner.java
@@ -14,32 +14,23 @@
   public void testScopedException() throws Exception {
     Class clazz = ScopedExceptionsTest.class;
     AndroidApp d8App = compileWithD8(clazz);
-    AndroidApp dxApp = getDxCompiledSources();
 
     String expected = "42";
     assertEquals(expected, runOnJava(clazz));
     assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
-    assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
 
-    checkScopedExceptions(inspectMethod(d8App, clazz, "int", "scopedExceptions"), false);
-    checkScopedExceptions(inspectMethod(dxApp, clazz, "int", "scopedExceptions"), true);
+    checkScopedExceptions(inspectMethod(d8App, clazz, "int", "scopedExceptions"));
   }
 
-  private void checkScopedExceptions(DebugInfoInspector info, boolean dx) {
+  private void checkScopedExceptions(DebugInfoInspector info) {
     info.checkStartLine(10);
     info.checkLineHasNoLocals(10);
     info.checkNoLine(11);
     info.checkLineHasNoLocals(12);
     info.checkLineHasNoLocals(13);
     info.checkLineHasExactLocals(14, "e", "java.lang.Throwable");
-    // DX does not generate a position at the end of the try-catch blocks, Java does and so does D8.
-    if (!dx) {
-      info.checkLineHasNoLocals(15);
-    }
+    info.checkLineHasNoLocals(15);
     info.checkLineExists(16);
-    // DX will still have an local entry for 'e' after its scope has ended.
-    if (!dx) {
-      info.checkLineHasNoLocals(16);
-    }
+    info.checkLineHasNoLocals(16);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTestRunner.java
index cd9e19d..f14d443 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTestRunner.java
@@ -25,26 +25,18 @@
   @Test
   public void testSynchronizedMethod() throws Exception {
     AndroidApp d8App = compileWithD8(clazz);
-    AndroidApp dxApp = getDxCompiledSources();
 
     String expected = StringUtils.lines("42", "42", "2", "2");
     assertEquals(expected, runOnJava(clazz));
     assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
-    assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
 
     checkSyncStatic(inspectMethod(d8App, clazz, "int", "syncStatic", "int"));
-    checkSyncStatic(inspectMethod(dxApp, clazz, "int", "syncStatic", "int"));
 
     checkSyncInstance(inspectMethod(d8App, clazz, "int", "syncInstance", "int"));
-    checkSyncInstance(inspectMethod(dxApp, clazz, "int", "syncInstance", "int"));
 
-    checkThrowing(inspectMethod(d8App, clazz, "int", "throwing", "int"), false);
-    checkThrowing(inspectMethod(dxApp, clazz, "int", "throwing", "int"), true);
+    checkThrowing(inspectMethod(d8App, clazz, "int", "throwing", "int"));
 
-    checkMonitorExitRegression(
-        inspectMethod(d8App, clazz, "int", "monitorExitRegression", "int"), false);
-    checkMonitorExitRegression(
-        inspectMethod(dxApp, clazz, "int", "monitorExitRegression", "int"), true);
+    checkMonitorExitRegression(inspectMethod(d8App, clazz, "int", "monitorExitRegression", "int"));
   }
 
   private void checkSyncStatic(DebugInfoInspector info) {
@@ -66,23 +58,18 @@
     info.checkNoLine(20);
   }
 
-  private void checkThrowing(DebugInfoInspector info, boolean dx) {
+  private void checkThrowing(DebugInfoInspector info) {
     info.checkStartLine(23);
-    if (!dx) {
-      info.checkLineHasExactLocals(23, "cond", "int");
-    }
+    info.checkLineHasExactLocals(23, "cond", "int");
     info.checkLineHasExactLocals(24, "cond", "int", "x", "int");
     info.checkLineHasExactLocals(25, "cond", "int", "x", "int");
     info.checkNoLine(26);
     info.checkLineHasExactLocals(27, "cond", "int", "x", "int");
   }
 
-  private void checkMonitorExitRegression(DebugInfoInspector info, boolean dx) {
+  private void checkMonitorExitRegression(DebugInfoInspector info) {
     info.checkStartLine(31);
     for (int line : Arrays.asList(32, 34, 36, 38, 40, 42, 44, 48, 50, 52)) {
-      if (dx && line == 40) {
-        continue;
-      }
       info.checkLineHasExactLocals(line, "cond", "int", "x", "int");
     }
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
index 9264433..254ef6d 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
 
-import static com.android.tools.r8.Collectors.toSingle;
+import static com.android.tools.r8.CollectorsUtils.toSingle;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
diff --git a/src/test/java/com/android/tools/r8/desugar/enclosingmethod/InvalidEnclosingMethodAttributeTest.java b/src/test/java/com/android/tools/r8/desugar/enclosingmethod/InvalidEnclosingMethodAttributeTest.java
new file mode 100644
index 0000000..6183472
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/enclosingmethod/InvalidEnclosingMethodAttributeTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.enclosingmethod;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.references.Reference;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.file.Path;
+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 InvalidEnclosingMethodAttributeTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public InvalidEnclosingMethodAttributeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class)
+        .addProgramClassFileData(getProgramClassFileDataWithRewrittenEnclosingMethod())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("null", typeName(Main.class));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class)
+        .addProgramClassFileData(getProgramClassFileDataWithRewrittenEnclosingMethod())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .addKeepAllClassesRule()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("null", typeName(Main.class));
+  }
+
+  private byte[] getProgramClassFileDataWithRewrittenEnclosingMethod() throws IOException {
+    Path innerClass = ToolHelper.getClassFilesForInnerClasses(Main.class).iterator().next();
+    return transformer(innerClass, Reference.classFromBinaryName(binaryName(Main.class) + "$1"))
+        .rewriteEnclosingMethod(binaryName(Main.class), "<clinit>", "()V")
+        .transform();
+  }
+
+  public static class Main {
+
+    public static Object a =
+        new Object() {
+          void foo() {
+            System.out.println("Hello World!");
+          }
+        };
+
+    public static void main(String[] args) {
+      Class<? extends Object> aClass = a.getClass();
+      Method enclosingMethod = aClass.getEnclosingMethod();
+      if (enclosingMethod == null) {
+        System.out.println("null");
+      } else {
+        System.out.println(enclosingMethod.getName());
+      }
+      Class<?> enclosingClass = aClass.getEnclosingClass();
+      if (enclosingClass == null) {
+        System.out.println("null");
+      } else {
+        System.out.println(enclosingClass.getName());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/DexDebugEventsTest.java b/src/test/java/com/android/tools/r8/graph/DexDebugEventsTest.java
index 75eb79f..4f71913 100644
--- a/src/test/java/com/android/tools/r8/graph/DexDebugEventsTest.java
+++ b/src/test/java/com/android/tools/r8/graph/DexDebugEventsTest.java
@@ -133,9 +133,9 @@
     List<DexDebugEvent> events = new ArrayList<>();
     DexDebugEventBuilder.emitAdvancementEvents(
         pc,
-        new Position(line, null, method, null),
+        Position.builder().setLine(line).setMethod(method).build(),
         nextPc,
-        new Position(nextLine, null, method, null),
+        Position.builder().setLine(nextLine).setMethod(method).build(),
         events,
         factory,
         false);
diff --git a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
index 83a8577..cec9053 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -154,7 +154,6 @@
       builder.setMode(mode);
       builder.setProgramConsumer(dexIndexedConsumerSupplier.get());
       builder.setMinApiLevel(AndroidApiLevel.L.getLevel());
-      ToolHelper.allowPartiallyImplementedProguardOptions(builder);
       ToolHelper.addProguardConfigurationConsumer(
           builder,
           pgConfig -> {
diff --git a/src/test/java/com/android/tools/r8/naming/IdentityMappingFileTest.java b/src/test/java/com/android/tools/r8/naming/IdentityMappingFileTest.java
new file mode 100644
index 0000000..21ece10
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/IdentityMappingFileTest.java
@@ -0,0 +1,126 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class IdentityMappingFileTest extends TestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public IdentityMappingFileTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  private void checkIdentityMappingContent(String mapping) {
+    assertThat(mapping, containsString("# compiler: R8"));
+    assertThat(mapping, containsString("# compiler_version: "));
+    assertThat(mapping, containsString("# min_api: 1"));
+    assertThat(mapping, containsString("# compiler_hash: "));
+    assertThat(mapping, containsString("# common_typos_disable"));
+    assertThat(mapping, containsString("# {\"id\":\"com.android.tools.r8.mapping\",\"version\":"));
+    assertThat(mapping, containsString("# pg_map_id: "));
+    assertThat(mapping, containsString("# pg_map_hash: SHA-256 "));
+    // Check the mapping is the identity, e.g., only comments are defined.
+    // Note, this could change if the mapping is ever changed to be complete, in which case the
+    // mapping will have actual identity mappings.
+    for (String line : StringUtils.splitLines(mapping)) {
+      assertThat(line, startsWith("#"));
+    }
+  }
+
+  @Test
+  public void testTheTestBuilder() throws Exception {
+    String mapping =
+        testForR8(Backend.DEX)
+            .addProgramClasses(Main.class)
+            .setMinApi(AndroidApiLevel.B)
+            .addKeepMainRule(Main.class)
+            .compile()
+            .getProguardMap();
+    checkIdentityMappingContent(mapping);
+  }
+
+  @Test
+  public void testFileOutput() throws Exception {
+    Path mappingPath = temp.newFolder().toPath().resolve("mapping.map");
+    R8.run(
+        R8Command.builder()
+            .addProgramFiles(ToolHelper.getClassFileForTestClass(Main.class))
+            .addProguardConfiguration(
+                ImmutableList.of(keepMainProguardConfiguration(Main.class)), Origin.unknown())
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+            .setProguardMapOutputPath(mappingPath)
+            .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+            .build());
+    assertTrue(Files.exists(mappingPath));
+    checkIdentityMappingContent(FileUtils.readTextFile(mappingPath, StandardCharsets.UTF_8));
+  }
+
+  @Test
+  public void testStringConsumer() throws Exception {
+    BooleanBox consumerWasCalled = new BooleanBox(false);
+    StringBuilder mappingContent = new StringBuilder();
+    R8.run(
+        R8Command.builder()
+            .addProgramFiles(ToolHelper.getClassFileForTestClass(Main.class))
+            .addProguardConfiguration(
+                ImmutableList.of(keepMainProguardConfiguration(Main.class)), Origin.unknown())
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+            .setProguardMapConsumer(
+                new StringConsumer() {
+                  @Override
+                  public void accept(String string, DiagnosticsHandler handler) {
+                    mappingContent.append(string);
+                  }
+
+                  @Override
+                  public void finished(DiagnosticsHandler handler) {
+                    consumerWasCalled.set(true);
+                  }
+                })
+            .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+            .build());
+    assertTrue(consumerWasCalled.get());
+    checkIdentityMappingContent(mappingContent.toString());
+  }
+
+  // Compiling this program with a keep main will result in an identity mapping for the residual
+  // program. The (identity) mapping should still be created and emitted to the client.
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java
index 9db718e..29bf9fa 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming.applymapping;
 
-import static com.android.tools.r8.Collectors.toSingle;
+import static com.android.tools.r8.CollectorsUtils.toSingle;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertNotSame;
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheck.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheck.java
index 232fe30..548025b 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheck.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheck.java
@@ -7,7 +7,6 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -53,7 +52,6 @@
         .addKeepAttributeSourceFile()
         .setMinApi(parameters.getApiLevel())
         .enableInliningAnnotations()
-        .enableNeverClassInliningAnnotations()
         .run(parameters.getRuntime(), Caller.class)
         .assertFailureWithErrorThatThrows(NullPointerException.class)
         // TODO(b/197936862): The two should be the same
@@ -75,7 +73,6 @@
     }
   }
 
-  @NeverClassInline
   static class Caller {
     @NeverInline
     static void caller(Foo f) {
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckSequence.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckSequence.java
new file mode 100644
index 0000000..f7f7b72
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceInlineeWithNullCheckSequence.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.retrace;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RetraceInlineeWithNullCheckSequence extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
+  }
+
+  public StackTrace expectedStackTrace;
+
+  @Before
+  public void setup() throws Exception {
+    // Get the expected stack trace by running on the JVM.
+    expectedStackTrace =
+        testForJvm()
+            .addTestClasspath()
+            .run(CfRuntime.getSystemRuntime(), Caller.class)
+            .assertFailure()
+            .map(StackTrace::extractFromJvm);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Caller.class)
+        .addKeepAttributeLineNumberTable()
+        .addKeepAttributeSourceFile()
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .run(parameters.getRuntime(), Caller.class)
+        .assertFailureWithErrorThatThrows(NullPointerException.class)
+        .inspectStackTrace(
+            (stackTrace, codeInspector) -> {
+              // TODO(b/197936862): The two stacktraces should be the same
+              assertThat(stackTrace, not(isSame(expectedStackTrace)));
+            });
+  }
+
+  static class Foo {
+    @NeverInline
+    void notInlinable() {
+      System.out.println("Hello, world!");
+      throw new RuntimeException("Foo");
+    }
+
+    void inlinable1() {
+      notInlinable();
+    }
+
+    void inlinable2() {
+      inlinable1();
+    }
+
+    void inlinable3() {
+      inlinable2();
+    }
+  }
+
+  static class Caller {
+
+    @NeverInline
+    static void caller(Foo f) {
+      f.inlinable3();
+    }
+
+    public static void main(String[] args) {
+      caller(System.currentTimeMillis() < 0 ? new Foo() : null);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java b/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
index 4cbaf6c..416835f 100644
--- a/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
+++ b/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
@@ -15,7 +15,8 @@
 import com.android.tools.r8.DataResourceConsumer;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.R8Command;
-import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersBuilder;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -51,12 +52,16 @@
 
   @Parameterized.Parameters(name = "Backend: {0}, Minify: {1}")
   public static Collection<Object[]> data() {
-    return buildParameters(ToolHelper.getBackends(), BooleanUtils.values());
+    return buildParameters(
+        ToolHelper.getBackends(),
+        BooleanUtils.values(),
+        TestParametersBuilder.builder().withNoneRuntime().build());
   }
 
-  public KeepDirectoriesTest(Backend backend, boolean minify) {
+  public KeepDirectoriesTest(Backend backend, boolean minify, TestParameters parameters) {
     this.backend = backend;
     this.minify = minify;
+    parameters.assertNoneRuntime();
   }
 
   // Return the original package name for this package.
@@ -66,14 +71,11 @@
 
   // Return the package name in the app for this package.
   private String pathForThisPackage(AndroidApp app) throws Exception {
-    String name;
-    if (app.getProguardMapOutputData() != null) {
-      ClassNameMapper mapper =
-          ClassNameMapper.mapperFromString(app.getProguardMapOutputData().getString());
-      name = mapper.getObfuscatedToOriginalMapping().inverse.get(Main.class.getCanonicalName());
-    } else {
-      name = Main.class.getTypeName();
-    }
+    ClassNameMapper mapper =
+        ClassNameMapper.mapperFromString(app.getProguardMapOutputData().getString());
+    String originalName = Main.class.getTypeName();
+    String name =
+        mapper.getObfuscatedToOriginalMapping().inverse.getOrDefault(originalName, originalName);
     return name.substring(0, name.lastIndexOf('.')).replace('.', '/');
   }
 
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
index 4bba10f..3fb600a 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceCommandLineTests.java
@@ -297,12 +297,13 @@
     if (testExternal) {
       // The external dependency is built on top of R8Lib. If test.py is run with
       // no r8lib, do not try and run the external R8 Retrace since it has not been built.
-      assumeTrue(Files.exists(ToolHelper.R8LIB_JAR));
+      assumeTrue(ToolHelper.isTestingR8Lib());
+      assertTrue(Files.exists(ToolHelper.R8LIB_JAR));
       List<String> command = new ArrayList<>();
       command.add(ToolHelper.getSystemJavaExecutable());
       command.add("-ea");
       command.add("-cp");
-      command.add(ToolHelper.R8_RETRACE_JAR.toString());
+      command.add(ToolHelper.R8LIB_JAR.toString());
       command.add("com.android.tools.r8.retrace.Retrace");
       command.addAll(args);
       ProcessBuilder builder = new ProcessBuilder(command);
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 34e4ca8..3bdef43 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -7,6 +7,7 @@
 import static junit.framework.TestCase.assertEquals;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
@@ -23,8 +24,7 @@
 import com.android.tools.r8.retrace.stacktraces.AmbiguousMissingLineStackTrace;
 import com.android.tools.r8.retrace.stacktraces.AmbiguousStackTrace;
 import com.android.tools.r8.retrace.stacktraces.AmbiguousWithMultipleLineMappingsStackTrace;
-import com.android.tools.r8.retrace.stacktraces.AmbiguousWithSignatureNonVerboseStackTrace;
-import com.android.tools.r8.retrace.stacktraces.AmbiguousWithSignatureVerboseStackTrace;
+import com.android.tools.r8.retrace.stacktraces.AmbiguousWithSignatureStackTrace;
 import com.android.tools.r8.retrace.stacktraces.AutoStackTrace;
 import com.android.tools.r8.retrace.stacktraces.CircularReferenceStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ColonInFileNameStackTrace;
@@ -33,6 +33,7 @@
 import com.android.tools.r8.retrace.stacktraces.FoundMethodVerboseStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineFileNameStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineFileNameWithInnerClassesStackTrace;
+import com.android.tools.r8.retrace.stacktraces.InlineInOutlineStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineNoLineNumberStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineSourceFileContextStackTrace;
 import com.android.tools.r8.retrace.stacktraces.InlineWithLineNumbersStackTrace;
@@ -48,6 +49,10 @@
 import com.android.tools.r8.retrace.stacktraces.NullStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ObfucatedExceptionClassStackTrace;
 import com.android.tools.r8.retrace.stacktraces.ObfuscatedRangeToSingleLineStackTrace;
+import com.android.tools.r8.retrace.stacktraces.OutlineInOutlineStackTrace;
+import com.android.tools.r8.retrace.stacktraces.OutlineSimpleStackTrace;
+import com.android.tools.r8.retrace.stacktraces.OutlineWithInliningStackTrace;
+import com.android.tools.r8.retrace.stacktraces.OutsideLineRangeStackTraceTest;
 import com.android.tools.r8.retrace.stacktraces.OverloadSameLineTest;
 import com.android.tools.r8.retrace.stacktraces.RetraceAssertionErrorStackTrace;
 import com.android.tools.r8.retrace.stacktraces.SingleLineNoLineNumberStackTrace;
@@ -177,8 +182,8 @@
   }
 
   @Test
-  public void testAmbiguousMissingLineNotVerbose() throws Exception {
-    runRetraceTest(new AmbiguousWithSignatureNonVerboseStackTrace());
+  public void testAmbiguousMissingLine() throws Exception {
+    runRetraceTest(new AmbiguousWithSignatureStackTrace());
   }
 
   @Test
@@ -308,11 +313,6 @@
   }
 
   @Test
-  public void testAmbiguousMissingLineVerbose() throws Exception {
-    runRetraceTest(new AmbiguousWithSignatureVerboseStackTrace());
-  }
-
-  @Test
   public void testNpeInlineRetraceStackTrace() throws Exception {
     runExperimentalRetraceTest(new NpeInlineRetraceStackTrace());
   }
@@ -327,6 +327,31 @@
     runRetraceTest(new DifferentLineNumberSpanStackTrace());
   }
 
+  @Test
+  public void testOutlineSimpleStackTrace() throws Exception {
+    runExperimentalRetraceTest(new OutlineSimpleStackTrace());
+  }
+
+  @Test
+  public void testOutlineWithInliningStackTrace() throws Exception {
+    runExperimentalRetraceTest(new OutlineWithInliningStackTrace());
+  }
+
+  @Test
+  public void testOutlineInOutlineStackTrace() throws Exception {
+    runExperimentalRetraceTest(new OutlineInOutlineStackTrace());
+  }
+
+  @Test
+  public void testInlineInOutlineStackTrace() throws Exception {
+    runExperimentalRetraceTest(new InlineInOutlineStackTrace());
+  }
+
+  @Test
+  public void testOutsideLineRangeStackTraceTest() throws Exception {
+    runRetraceTest(new OutsideLineRangeStackTraceTest());
+  }
+
   private void inspectRetraceTest(
       StackTraceForTest stackTraceForTest, Consumer<Retracer> inspection) {
     inspection.accept(
@@ -356,7 +381,8 @@
       assumeTrue(testParameters.isCfRuntime());
       // The external dependency is built on top of R8Lib. If test.py is run with
       // no r8lib, do not try and run the external R8 Retrace since it has not been built.
-      assumeTrue(Files.exists(ToolHelper.R8LIB_JAR));
+      assumeTrue(ToolHelper.isTestingR8Lib());
+      assertTrue(Files.exists(ToolHelper.R8LIB_JAR));
       Path path = temp.newFolder().toPath();
       Path mappingFile = path.resolve("mapping");
       Files.write(mappingFile, stackTraceForTest.mapping().getBytes());
@@ -370,7 +396,7 @@
       command.add(testParameters.getRuntime().asCf().getJavaExecutable().toString());
       command.add("-ea");
       command.add("-cp");
-      command.add(ToolHelper.R8_RETRACE_JAR.toString());
+      command.add(ToolHelper.R8LIB_JAR.toString());
       if (allowExperimentalMapping) {
         command.add("-Dcom.android.tools.r8.experimentalmapping");
       }
diff --git a/src/test/java/com/android/tools/r8/retrace/StackTraceRegularExpressionParserTests.java b/src/test/java/com/android/tools/r8/retrace/StackTraceRegularExpressionParserTests.java
index 9af1378..b769752 100644
--- a/src/test/java/com/android/tools/r8/retrace/StackTraceRegularExpressionParserTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/StackTraceRegularExpressionParserTests.java
@@ -517,12 +517,12 @@
 
           @Override
           public List<String> retracedStackTrace() {
-            return ImmutableList.of("com.android.tools.r8.R8.foo(42)");
+            return ImmutableList.of("com.android.tools.r8.R8.a(42)");
           }
 
           @Override
           public List<String> retraceVerboseStackTrace() {
-            return ImmutableList.of("com.android.tools.r8.R8.boolean foo()(42)");
+            return ImmutableList.of("com.android.tools.r8.R8.a(42)");
           }
 
           @Override
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiAmbiguousOriginalRangeTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiAmbiguousOriginalRangeTest.java
new file mode 100644
index 0000000..3f616f0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiAmbiguousOriginalRangeTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.api;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.RetraceFrameResult;
+import com.android.tools.r8.retrace.Retracer;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.OptionalInt;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RetraceApiAmbiguousOriginalRangeTest extends RetraceApiTestBase {
+
+  public RetraceApiAmbiguousOriginalRangeTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  protected Class<? extends RetraceApiBinaryTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  public static class ApiTest implements RetraceApiBinaryTest {
+
+    private final ClassReference renamedHolder = Reference.classFromTypeName("a");
+
+    private final String mapping =
+        "com.android.tools.r8.naming.retrace.Main -> a:\n"
+            + "  1:1:void method():42:44 -> a\n"
+            + "  2:4:void method():45:46 -> a\n"
+            + "  5:6:void method():47:47 -> a\n";
+
+    @Test
+    public void testAmbiguousResult() {
+      Retracer retracer =
+          Retracer.createDefault(
+              ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {});
+      MethodReference methodReference =
+          Reference.methodFromDescriptor(renamedHolder.getDescriptor(), "a", "()V");
+
+      // Check that retracing with position one is ambiguous between line 42, 43 and 44.
+      RetraceFrameResult retraceFrameResult =
+          retracer.retraceFrame(methodReference, OptionalInt.of(1));
+      assertTrue(retraceFrameResult.isAmbiguous());
+      List<Integer> originalPositions =
+          retraceFrameResult.stream()
+              .map(element -> element.getTopFrame().getOriginalPositionOrDefault(1))
+              .collect(Collectors.toList());
+      assertEquals(Arrays.asList(42, 43, 44), originalPositions);
+
+      // Check that retracing with position 3 is ambiguous between 45 and 46.
+      retraceFrameResult = retracer.retraceFrame(methodReference, OptionalInt.of(3));
+      assertTrue(retraceFrameResult.isAmbiguous());
+      originalPositions =
+          retraceFrameResult.stream()
+              .map(element -> element.getTopFrame().getOriginalPositionOrDefault(3))
+              .collect(Collectors.toList());
+      assertEquals(Arrays.asList(45, 46), originalPositions);
+
+      // Check that retracing with position 5 is not ambiguous.
+      retraceFrameResult = retracer.retraceFrame(methodReference, OptionalInt.of(5));
+      assertFalse(retraceFrameResult.isAmbiguous());
+      originalPositions =
+          retraceFrameResult.stream()
+              .map(element -> element.getTopFrame().getOriginalPositionOrDefault(5))
+              .collect(Collectors.toList());
+      assertEquals(Collections.singletonList(47), originalPositions);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiBinaryCompatibilityTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiBinaryCompatibilityTest.java
index 4b8f03f..a4983b2 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiBinaryCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiBinaryCompatibilityTest.java
@@ -4,25 +4,9 @@
 
 package com.android.tools.r8.retrace.api;
 
-import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.assertTrue;
-
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRuntime.CfRuntime;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.utils.ZipUtils;
-import com.google.common.collect.ImmutableList;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
@@ -32,74 +16,23 @@
 @RunWith(Parameterized.class)
 public class RetraceApiBinaryCompatibilityTest extends TestBase {
 
-  private final TestParameters parameters;
-
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withNoneRuntime().build();
   }
 
   public RetraceApiBinaryCompatibilityTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
-
-  private static final Path BINARY_COMPATIBILITY_JAR =
-      Paths.get(ToolHelper.THIRD_PARTY_DIR, "retrace", "binary_compatibility", "tests.jar");
-
-  private Path generateJar() throws Exception {
-    return RetraceApiTestHelper.generateJarForRetraceBinaryTests(
-        temp, RetraceApiTestHelper.getBinaryCompatibilityTests());
+    parameters.assertNoneRuntime();
   }
 
   @Test
   public void testBinaryJarIsUpToDate() throws Exception {
-    Path binaryContents = temp.newFolder().toPath();
-    Path generatedContents = temp.newFolder().toPath();
-    ZipUtils.unzip(BINARY_COMPATIBILITY_JAR, binaryContents);
-    ZipUtils.unzip(generateJar(), generatedContents);
-    try (Stream<Path> existingPaths = Files.walk(binaryContents);
-        Stream<Path> generatedPaths = Files.walk(generatedContents)) {
-      List<Path> existing = existingPaths.filter(this::isClassFile).collect(Collectors.toList());
-      List<Path> generated = generatedPaths.filter(this::isClassFile).collect(Collectors.toList());
-      assertEquals(existing.size(), generated.size());
-      assertNotEquals(0, existing.size());
-      for (Path classFile : generated) {
-        Path otherClassFile = binaryContents.resolve(generatedContents.relativize(classFile));
-        assertTrue("Could not find file: " + otherClassFile, Files.exists(otherClassFile));
-        assertTrue(
-            "Non-equal files: " + otherClassFile,
-            TestBase.filesAreEqual(classFile, otherClassFile));
-      }
-    }
-  }
-
-  private boolean isClassFile(Path path) {
-    return path.toString().endsWith(".class");
+    new RetraceApiTestCollection(temp).verifyCheckedInJarIsUpToDate();
   }
 
   @Test
   public void runCheckedInBinaryJar() throws Exception {
-    // The retrace jar is only built when building r8lib.
-    Path jar = ToolHelper.isTestingR8Lib() ? ToolHelper.R8_RETRACE_JAR : ToolHelper.R8_JAR;
-    RetraceApiTestHelper.runJunitOnTests(
-        CfRuntime.getSystemRuntime(),
-        jar,
-        BINARY_COMPATIBILITY_JAR,
-        RetraceApiTestHelper.getBinaryCompatibilityTests());
-  }
-
-  @Test
-  public void runCheckedInWithNonExistingTest() {
-    Path jar = ToolHelper.isTestingR8Lib() ? ToolHelper.R8_RETRACE_JAR : ToolHelper.R8_JAR;
-    assertThrows(
-        AssertionError.class,
-        () -> {
-          RetraceApiTestHelper.runJunitOnTests(
-              CfRuntime.getSystemRuntime(),
-              jar,
-              BINARY_COMPATIBILITY_JAR,
-              ImmutableList.of(new RetraceApiBinaryTest() {}.getClass()));
-        });
+    new RetraceApiTestCollection(temp).runJunitOnCheckedInJar();
   }
 
   /**
@@ -109,9 +42,6 @@
   public static void main(String[] args) throws Exception {
     TemporaryFolder temp = new TemporaryFolder();
     temp.create();
-    Path generatedJar =
-        RetraceApiTestHelper.generateJarForRetraceBinaryTests(
-            temp, RetraceApiTestHelper.getBinaryCompatibilityTests());
-    Files.move(generatedJar, BINARY_COMPATIBILITY_JAR, REPLACE_EXISTING);
+    new RetraceApiTestCollection(temp).replaceJarForCheckedInTestClasses();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInlineInOutlineTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInlineInOutlineTest.java
new file mode 100644
index 0000000..7cce489
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInlineInOutlineTest.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.api;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.RetraceFrameElement;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.retrace.RetracedSingleFrame;
+import com.android.tools.r8.retrace.Retracer;
+import java.util.List;
+import java.util.OptionalInt;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RetraceApiInlineInOutlineTest extends RetraceApiTestBase {
+
+  public RetraceApiInlineInOutlineTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  protected Class<? extends RetraceApiBinaryTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  public static class ApiTest implements RetraceApiBinaryTest {
+
+    private final ClassReference outlineRenamed = Reference.classFromTypeName("a");
+    private final ClassReference callsiteOriginal = Reference.classFromTypeName("some.Class");
+    private final ClassReference callsiteRenamed = Reference.classFromTypeName("b");
+
+    private final String mapping =
+        "# { id: 'com.android.tools.r8.mapping', version: 'experimental' }\n"
+            + "outline.Class -> "
+            + outlineRenamed.getTypeName()
+            + ":\n"
+            + "  1:2:int some.inlinee():75:76 -> a\n"
+            + "  1:2:int outline():0 -> a\n"
+            + "# { 'id':'com.android.tools.r8.outline' }\n"
+            + callsiteOriginal.getTypeName()
+            + " -> "
+            + callsiteRenamed.getTypeName()
+            + ":\n"
+            + "  1:1:void foo.bar.Baz.qux():42:42 -> s\n"
+            + "  5:5:int foo.bar.baz.outlineCaller(int):98:98 -> s\n"
+            + "  5:5:int outlineCaller(int):24 -> s\n"
+            + "  27:27:int outlineCaller(int):0:0 -> s\n"
+            + "# { 'id':'com.android.tools.r8.outlineCallsite', "
+            + "    'positions': { '1': 4, '2': 5 } }\n";
+
+    @Test
+    public void test() {
+      Retracer retracer =
+          Retracer.createExperimental(
+              ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {});
+      List<RetraceFrameElement> outlineRetraced =
+          retracer
+              .retraceFrame(
+                  Reference.methodFromDescriptor(outlineRenamed, "a", "()I"), OptionalInt.of(2))
+              .stream()
+              .collect(Collectors.toList());
+      // The retrace result should not be ambiguous or empty.
+      assertEquals(1, outlineRetraced.size());
+      RetraceFrameElement retraceFrameElement = outlineRetraced.get(0);
+
+      // Check that visiting all frames report the outline.
+      List<RetracedMethodReference> allMethodReferences =
+          retraceFrameElement.stream()
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
+      assertEquals(2, allMethodReferences.size());
+      assertEquals(76, allMethodReferences.get(0).getOriginalPositionOrDefault(2));
+      assertEquals(0, allMethodReferences.get(1).getOriginalPositionOrDefault(2));
+
+      // Check that visiting rewritten frames will not report anything.
+      List<RetracedMethodReference> rewrittenReferences =
+          retraceFrameElement
+              .streamRewritten(RetraceStackTraceContext.empty())
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
+      assertEquals(1, rewrittenReferences.size());
+      assertEquals(76, allMethodReferences.get(0).getOriginalPositionOrDefault(2));
+
+      // Retrace the outline position
+      RetraceStackTraceContext context = retraceFrameElement.getRetraceStackTraceContext();
+      List<RetraceFrameElement> retraceOutlineCallee =
+          retracer
+              .retraceFrame(
+                  Reference.methodFromDescriptor(callsiteRenamed, "s", "(I)V"),
+                  OptionalInt.of(27),
+                  context)
+              .stream()
+              .collect(Collectors.toList());
+      assertEquals(1, retraceOutlineCallee.size());
+
+      List<RetracedMethodReference> outlineCallSiteFrames =
+          retraceOutlineCallee.get(0).stream()
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
+      assertEquals(2, outlineCallSiteFrames.size());
+      assertEquals(98, outlineCallSiteFrames.get(0).getOriginalPositionOrDefault(27));
+      assertEquals(24, outlineCallSiteFrames.get(1).getOriginalPositionOrDefault(27));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInOutlineStackTrace.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInOutlineStackTrace.java
new file mode 100644
index 0000000..c2b5c47
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInOutlineStackTrace.java
@@ -0,0 +1,141 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.api;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.RetraceFrameElement;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.retrace.RetracedSingleFrame;
+import com.android.tools.r8.retrace.Retracer;
+import java.util.List;
+import java.util.OptionalInt;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RetraceApiOutlineInOutlineStackTrace extends RetraceApiTestBase {
+
+  public RetraceApiOutlineInOutlineStackTrace(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  protected Class<? extends RetraceApiBinaryTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  public static class ApiTest implements RetraceApiBinaryTest {
+
+    private final ClassReference outline1Renamed = Reference.classFromTypeName("a");
+    private final ClassReference outline2Renamed = Reference.classFromTypeName("b");
+    private final ClassReference callsiteOriginal = Reference.classFromTypeName("some.Class");
+    private final ClassReference callsiteRenamed = Reference.classFromTypeName("c");
+
+    private final String mapping =
+        "# { id: 'com.android.tools.r8.mapping', version: 'experimental' }\n"
+            + "outline1.Class -> "
+            + outline1Renamed.getTypeName()
+            + ":\n"
+            + "  3:4:int outline():0:0 -> a\n"
+            + "# { 'id':'com.android.tools.r8.outline' }\n"
+            + "outline2.Class -> "
+            + outline2Renamed.getTypeName()
+            + ":\n"
+            + "  6:6:int outline():0:0 -> a\n"
+            + "# { 'id':'com.android.tools.r8.outlineCallsite', "
+            + "     'positions': { '3': 42, '4': 43 } }\n"
+            + "  42:43:int outline():0:0 -> a\n" // This is another call to the outline
+            + "# { 'id':'com.android.tools.r8.outline' }\n"
+            + callsiteOriginal.getTypeName()
+            + " -> "
+            + callsiteRenamed.getTypeName()
+            + ":\n"
+            + "  1:1:void foo.bar.Baz.qux():42:42 -> s\n"
+            + "  10:11:int foo.bar.baz.outlineCaller(int):98:99 -> s\n"
+            + "  28:28:int outlineCaller(int):0:0 -> s\n" // This is the actual call to the outline
+            + "# { 'id':'com.android.tools.r8.outlineCallsite', "
+            + "     'positions': { '42': 10, '43': 11 } }\n";
+
+    @Test
+    public void test() {
+      Retracer retracer =
+          Retracer.createExperimental(
+              ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {});
+      // Retrace the first outline.
+      RetraceStackTraceContext outlineContext =
+          retraceOutline(
+              retracer,
+              Reference.methodFromDescriptor(outline1Renamed, "a", "()I"),
+              4,
+              RetraceStackTraceContext.empty());
+
+      // Retrace the second outline using the context of the first retracing.
+      outlineContext =
+          retraceOutline(
+              retracer,
+              Reference.methodFromDescriptor(outline2Renamed, "a", "()I"),
+              6,
+              outlineContext);
+
+      List<RetraceFrameElement> retraceOutlineCallee =
+          retracer
+              .retraceFrame(
+                  Reference.methodFromDescriptor(callsiteRenamed, "s", "(I)V"),
+                  OptionalInt.of(28),
+                  outlineContext)
+              .stream()
+              .collect(Collectors.toList());
+      assertEquals(1, retraceOutlineCallee.size());
+
+      List<RetracedMethodReference> outlineCallSiteFrames =
+          retraceOutlineCallee.get(0).stream()
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
+      assertEquals(1, outlineCallSiteFrames.size());
+      assertEquals(99, outlineCallSiteFrames.get(0).getOriginalPositionOrDefault(28));
+    }
+
+    private RetraceStackTraceContext retraceOutline(
+        Retracer retracer,
+        MethodReference reference,
+        int position,
+        RetraceStackTraceContext context) {
+      List<RetraceFrameElement> outlineRetraced =
+          retracer.retraceFrame(reference, OptionalInt.of(position), context).stream()
+              .collect(Collectors.toList());
+      // The retrace result should not be ambiguous or empty.
+      assertEquals(1, outlineRetraced.size());
+      RetraceFrameElement retraceFrameElement = outlineRetraced.get(0);
+
+      // Check that visiting all frames report the outline.
+      List<RetracedMethodReference> allMethodReferences =
+          retraceFrameElement.stream()
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
+      assertEquals(1, allMethodReferences.size());
+      assertEquals(0, allMethodReferences.get(0).getOriginalPositionOrDefault(position));
+
+      // Check that visiting rewritten frames will not report anything.
+      List<RetracedMethodReference> rewrittenReferences =
+          retraceFrameElement
+              .streamRewritten(RetraceStackTraceContext.empty())
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
+      assertEquals(0, rewrittenReferences.size());
+
+      return retraceFrameElement.getRetraceStackTraceContext();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInlineTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInlineTest.java
new file mode 100644
index 0000000..6d7fe97
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInlineTest.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.api;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.RetraceFrameElement;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.retrace.RetracedSingleFrame;
+import com.android.tools.r8.retrace.Retracer;
+import java.util.List;
+import java.util.OptionalInt;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RetraceApiOutlineInlineTest extends RetraceApiTestBase {
+
+  public RetraceApiOutlineInlineTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  protected Class<? extends RetraceApiBinaryTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  public static class ApiTest implements RetraceApiBinaryTest {
+
+    private final ClassReference outlineRenamed = Reference.classFromTypeName("a");
+    private final ClassReference callsiteOriginal = Reference.classFromTypeName("some.Class");
+    private final ClassReference callsiteRenamed = Reference.classFromTypeName("b");
+
+    private final String mapping =
+        "# { id: 'com.android.tools.r8.mapping', version: 'experimental' }\n"
+            + "outline.Class -> "
+            + outlineRenamed.getTypeName()
+            + ":\n"
+            + "  1:2:int outline():0:0 -> a\n"
+            + "# { 'id':'com.android.tools.r8.outline' }\n"
+            + callsiteOriginal.getTypeName()
+            + " -> "
+            + callsiteRenamed.getTypeName()
+            + ":\n"
+            + "  1:1:void foo.bar.Baz.qux():42:42 -> s\n"
+            + "  4:4:int foo.bar.baz.outlineCaller(int):98:98 -> s\n"
+            + "  4:4:int outlineCaller(int):24 -> s\n"
+            + "  27:27:int outlineCaller(int):0:0 -> s\n" // This is the actual call to the outline
+            + "# { 'id':'com.android.tools.r8.outlineCallsite', "
+            + "    'positions': { '1': 4, '2': 5 } }\n";
+
+    @Test
+    public void test() {
+      Retracer retracer =
+          Retracer.createExperimental(
+              ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {});
+      List<RetraceFrameElement> outlineRetraced =
+          retracer
+              .retraceFrame(
+                  Reference.methodFromDescriptor(outlineRenamed, "a", "()I"), OptionalInt.of(1))
+              .stream()
+              .collect(Collectors.toList());
+      // The retrace result should not be ambiguous or empty.
+      assertEquals(1, outlineRetraced.size());
+      RetraceFrameElement retraceFrameElement = outlineRetraced.get(0);
+
+      // Check that visiting all frames report the outline.
+      List<RetracedMethodReference> allMethodReferences =
+          retraceFrameElement.stream()
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
+      assertEquals(1, allMethodReferences.size());
+      assertEquals(0, allMethodReferences.get(0).getOriginalPositionOrDefault(2));
+
+      // Check that visiting rewritten frames will not report anything.
+      List<RetracedMethodReference> rewrittenReferences =
+          retraceFrameElement
+              .streamRewritten(RetraceStackTraceContext.empty())
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
+      assertEquals(0, rewrittenReferences.size());
+
+      // Retrace the outline position
+      RetraceStackTraceContext context = retraceFrameElement.getRetraceStackTraceContext();
+      List<RetraceFrameElement> retraceOutlineCallee =
+          retracer
+              .retraceFrame(
+                  Reference.methodFromDescriptor(callsiteRenamed, "s", "(I)V"),
+                  OptionalInt.of(27),
+                  context)
+              .stream()
+              .collect(Collectors.toList());
+      assertEquals(1, retraceOutlineCallee.size());
+
+      List<RetracedMethodReference> outlineCallSiteFrames =
+          retraceOutlineCallee.get(0).stream()
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
+      assertEquals(2, outlineCallSiteFrames.size());
+      assertEquals(98, outlineCallSiteFrames.get(0).getOriginalPositionOrDefault(27));
+      assertEquals(24, outlineCallSiteFrames.get(1).getOriginalPositionOrDefault(27));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineNoInlineTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineNoInlineTest.java
new file mode 100644
index 0000000..6330587
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineNoInlineTest.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.api;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.RetraceFrameElement;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.retrace.RetracedSingleFrame;
+import com.android.tools.r8.retrace.Retracer;
+import java.util.List;
+import java.util.OptionalInt;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RetraceApiOutlineNoInlineTest extends RetraceApiTestBase {
+
+  public RetraceApiOutlineNoInlineTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  protected Class<? extends RetraceApiBinaryTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  public static class ApiTest implements RetraceApiBinaryTest {
+
+    private final ClassReference outlineRenamed = Reference.classFromTypeName("a");
+    private final ClassReference callsiteOriginal = Reference.classFromTypeName("some.Class");
+    private final ClassReference callsiteRenamed = Reference.classFromTypeName("b");
+
+    private final String mapping =
+        "# { id: 'com.android.tools.r8.mapping', version: 'experimental' }\n"
+            + "outline.Class -> "
+            + outlineRenamed.getTypeName()
+            + ":\n"
+            + "  1:2:int outline():0:0 -> a\n"
+            + "# { 'id':'com.android.tools.r8.outline' }\n"
+            + callsiteOriginal.getTypeName()
+            + " -> "
+            + callsiteRenamed.getTypeName()
+            + ":\n"
+            + "  1:1:void foo.bar.Baz.qux():42:42 -> s\n"
+            + "  4:4:int outlineCaller(int):98:98 -> s\n"
+            + "  27:27:int outlineCaller(int):0:0 -> s\n" // This is the actual call to the outline
+            + "# { 'id':'com.android.tools.r8.outlineCallsite', "
+            + "    'positions': { '1': 4, '2': 5 } }\n";
+
+    @Test
+    public void test() {
+      Retracer retracer =
+          Retracer.createExperimental(
+              ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {});
+      List<RetraceFrameElement> outlineRetraced =
+          retracer
+              .retraceFrame(
+                  Reference.methodFromDescriptor(outlineRenamed, "a", "()I"), OptionalInt.of(1))
+              .stream()
+              .collect(Collectors.toList());
+      // The retrace result should not be ambiguous or empty.
+      assertEquals(1, outlineRetraced.size());
+      RetraceFrameElement retraceFrameElement = outlineRetraced.get(0);
+
+      assertTrue(retraceFrameElement.isCompilerSynthesized());
+
+      // Check that visiting all frames report the outline.
+      List<RetracedMethodReference> allMethodReferences =
+          retraceFrameElement.stream()
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
+      assertEquals(1, allMethodReferences.size());
+      assertEquals(0, allMethodReferences.get(0).getOriginalPositionOrDefault(2));
+
+      // Check that visiting rewritten frames will not report anything.
+      List<RetracedMethodReference> rewrittenReferences =
+          retraceFrameElement
+              .streamRewritten(RetraceStackTraceContext.empty())
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
+      assertEquals(0, rewrittenReferences.size());
+
+      // Retrace the outline position
+      RetraceStackTraceContext context = retraceFrameElement.getRetraceStackTraceContext();
+      List<RetraceFrameElement> retraceOutlineCallee =
+          retracer
+              .retraceFrame(
+                  Reference.methodFromDescriptor(callsiteRenamed, "s", "(I)V"),
+                  OptionalInt.of(27),
+                  context)
+              .stream()
+              .collect(Collectors.toList());
+      assertEquals(1, retraceOutlineCallee.size());
+
+      List<RetracedMethodReference> outlineCallSiteFrames =
+          retraceOutlineCallee.get(0).stream()
+              .map(RetracedSingleFrame::getMethodReference)
+              .collect(Collectors.toList());
+      assertEquals(1, outlineCallSiteFrames.size());
+      assertEquals(98, outlineCallSiteFrames.get(0).getOriginalPositionOrDefault(27));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutsideLineRangeTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutsideLineRangeTest.java
new file mode 100644
index 0000000..a3ba721
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutsideLineRangeTest.java
@@ -0,0 +1,91 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.api;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.RetraceClassElement;
+import com.android.tools.r8.retrace.RetraceFrameElement;
+import com.android.tools.r8.retrace.RetraceMethodElement;
+import com.android.tools.r8.retrace.RetraceMethodResult;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.Retracer;
+import java.util.List;
+import java.util.OptionalInt;
+import java.util.stream.Collectors;
+import org.junit.Test;
+
+public class RetraceApiOutsideLineRangeTest extends RetraceApiTestBase {
+
+  public RetraceApiOutsideLineRangeTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  protected Class<? extends RetraceApiBinaryTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  public static class ApiTest implements RetraceApiBinaryTest {
+
+    private final ClassReference someOtherClassOriginal =
+        Reference.classFromTypeName("some.other.Class");
+    private final ClassReference someOtherClassRenamed = Reference.classFromTypeName("a");
+    private final ClassReference someClassOriginal = Reference.classFromTypeName("some.Class");
+    private final ClassReference someClassRenamed = Reference.classFromTypeName("b");
+
+    private final String mapping =
+        someOtherClassOriginal.getTypeName()
+            + " -> "
+            + someOtherClassRenamed.getTypeName()
+            + ":\n"
+            + "  void method1():42:42 -> a\n"
+            + someClassOriginal.getTypeName()
+            + " -> "
+            + someClassRenamed.getTypeName()
+            + ":\n"
+            + "  1:3:void method2():11:13 -> a\n"
+            + "  4:4:void method2():10:10 -> a\n"
+            + "  28:28:void foo.bar.inlinee():92:92 -> a\n"
+            + "  5:5:void method2():14 -> a\n";
+
+    @Test
+    public void testNoLine() {
+      Retracer retracer =
+          Retracer.createDefault(
+              ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {});
+      retraceClassMethodAndPosition(retracer, someClassRenamed, someClassOriginal, 2, 0);
+      retraceClassMethodAndPosition(retracer, someOtherClassRenamed, someOtherClassOriginal, 1, 1);
+    }
+
+    private void retraceClassMethodAndPosition(
+        Retracer retracer,
+        ClassReference renamedClass,
+        ClassReference originalClass,
+        int methodCount,
+        int frameCount) {
+      List<RetraceClassElement> classResult =
+          retracer.retraceClass(renamedClass).stream().collect(Collectors.toList());
+      assertEquals(classResult.size(), 1);
+      RetraceClassElement retraceClassResult = classResult.get(0);
+      assertEquals(originalClass, retraceClassResult.getRetracedClass().getClassReference());
+      RetraceMethodResult retraceMethodResult = retraceClassResult.lookupMethod("a");
+      List<RetraceMethodElement> classMethodResults =
+          retraceMethodResult.stream().collect(Collectors.toList());
+      assertEquals(methodCount, classMethodResults.size());
+      List<RetraceFrameElement> classResultFrames =
+          retraceMethodResult
+              .narrowByPosition(RetraceStackTraceContext.empty(), OptionalInt.of(6))
+              .stream()
+              .collect(Collectors.toList());
+      assertEquals(frameCount, classResultFrames.size());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSingleFrameTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSingleFrameTest.java
new file mode 100644
index 0000000..bec1a7f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiSingleFrameTest.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.api;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.RetraceFrameElement;
+import com.android.tools.r8.retrace.RetraceStackTraceContext;
+import com.android.tools.r8.retrace.RetracedMethodReference;
+import com.android.tools.r8.retrace.Retracer;
+import java.util.List;
+import java.util.OptionalInt;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RetraceApiSingleFrameTest extends RetraceApiTestBase {
+
+  public RetraceApiSingleFrameTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  protected Class<? extends RetraceApiBinaryTest> binaryTestClass() {
+    return ApiTest.class;
+  }
+
+  public static class ApiTest implements RetraceApiBinaryTest {
+
+    private final ClassReference originalClass = Reference.classFromTypeName("some.Class");
+    private final ClassReference renamedClass = Reference.classFromTypeName("a");
+
+    private final String mapping =
+        originalClass.getTypeName()
+            + " -> "
+            + renamedClass.getTypeName()
+            + ":\n"
+            + "  int strawberry(int):99:99 -> a\n";
+
+    @Test
+    public void testRetracingFrameEqualToNarrow() {
+      Retracer retracer =
+          Retracer.createDefault(
+              ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {});
+      checkResults(
+          retracer
+              .retraceFrame(
+                  Reference.methodFromDescriptor(renamedClass, "a", "()I"), OptionalInt.empty())
+              .stream()
+              .collect(Collectors.toList()));
+      checkResults(
+          retracer
+              .retraceClass(renamedClass)
+              .lookupFrame(RetraceStackTraceContext.empty(), OptionalInt.empty(), "a")
+              .stream()
+              .collect(Collectors.toList()));
+    }
+
+    private void checkResults(List<RetraceFrameElement> elements) {
+      assertEquals(1, elements.size());
+      RetracedMethodReference topFrame = elements.get(0).getTopFrame();
+      assertEquals("strawberry", topFrame.getMethodName());
+      assertEquals(99, topFrame.getOriginalPositionOrDefault(-1));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestBase.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestBase.java
index a58102a..4dcd86a 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestBase.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestBase.java
@@ -4,50 +4,31 @@
 
 package com.android.tools.r8.retrace.api;
 
-import static com.android.tools.r8.retrace.api.RetraceApiTestHelper.runJunitOnTests;
-import static org.junit.Assert.assertTrue;
-
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersBuilder;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper;
-import java.nio.file.Files;
-import org.junit.Assume;
 import org.junit.Test;
-import org.junit.runner.JUnitCore;
-import org.junit.runner.Result;
-import org.junit.runner.notification.Failure;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
+@RunWith(Parameterized.class)
 public abstract class RetraceApiTestBase extends TestBase {
 
-  private final TestParameters parameters;
-
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return TestParametersBuilder.builder().withSystemRuntime().build();
+    return TestParametersBuilder.builder().withNoneRuntime().build();
   }
 
   public RetraceApiTestBase(TestParameters parameters) {
-    this.parameters = parameters;
+    parameters.assertNoneRuntime();
   }
 
   protected abstract Class<? extends RetraceApiBinaryTest> binaryTestClass();
 
   @Test
-  public void testDirect() {
-    Result result = JUnitCore.runClasses(binaryTestClass());
-    for (Failure failure : result.getFailures()) {
-      System.out.println(failure.toString());
-    }
-    assertTrue(result.wasSuccessful());
-  }
-
-  @Test
-  public void testRetraceLib() throws Exception {
-    Assume.assumeTrue(Files.exists(ToolHelper.R8_RETRACE_JAR));
-    runJunitOnTests(
-        parameters.getRuntime().asCf(), ToolHelper.R8_RETRACE_JAR, binaryTestClass(), temp);
+  public void testExternal() throws Exception {
+    new RetraceApiTestCollection(temp).runJunitOnTestClass(binaryTestClass());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
new file mode 100644
index 0000000..77b8aea
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestCollection.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.api;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.compilerapi.BinaryCompatibilityTestCollection;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.rules.TemporaryFolder;
+
+public class RetraceApiTestCollection
+    extends BinaryCompatibilityTestCollection<RetraceApiBinaryTest> {
+
+  private static final Path BINARY_COMPATIBILITY_JAR =
+      Paths.get(ToolHelper.THIRD_PARTY_DIR, "retrace", "binary_compatibility", "tests.jar");
+
+  public static List<Class<? extends RetraceApiBinaryTest>> CLASSES_FOR_BINARY_COMPATIBILITY =
+      ImmutableList.of(
+          RetraceApiEmptyTest.RetraceTest.class,
+          RetraceApiSourceFileTest.ApiTest.class,
+          RetraceApiSourceFileNotFoundTest.ApiTest.class,
+          RetraceApiInferSourceFileTest.ApiTest.class,
+          RetraceApiSynthesizedClassTest.ApiTest.class,
+          RetraceApiSynthesizedFieldTest.ApiTest.class,
+          RetraceApiSynthesizedMethodTest.ApiTest.class,
+          RetraceApiSynthesizedFrameTest.ApiTest.class,
+          RetraceApiSynthesizedInnerFrameTest.ApiTest.class,
+          RetraceApiUnknownJsonTest.ApiTest.class,
+          RetraceApiRewriteFrameInlineNpeTest.ApiTest.class,
+          RetraceApiAmbiguousOriginalRangeTest.ApiTest.class,
+          RetraceApiOutsideLineRangeTest.ApiTest.class);
+
+  public static List<Class<? extends RetraceApiBinaryTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
+      ImmutableList.of(
+          RetraceApiRewriteFrameInlineNpeResidualTest.ApiTest.class,
+          RetraceApiOutlineNoInlineTest.ApiTest.class,
+          RetraceApiOutlineInlineTest.ApiTest.class,
+          RetraceApiOutlineInOutlineStackTrace.ApiTest.class,
+          RetraceApiInlineInOutlineTest.ApiTest.class,
+          RetraceApiSingleFrameTest.ApiTest.class);
+
+  private final TemporaryFolder temp;
+
+  public RetraceApiTestCollection(TemporaryFolder temp) {
+    this.temp = temp;
+  }
+
+  @Override
+  public TemporaryFolder getTemp() {
+    return temp;
+  }
+
+  @Override
+  public Path getTargetJar() {
+    return ToolHelper.isTestingR8Lib() ? ToolHelper.R8LIB_JAR : ToolHelper.R8_JAR;
+  }
+
+  @Override
+  public Path getCheckedInTestJar() {
+    return BINARY_COMPATIBILITY_JAR;
+  }
+
+  @Override
+  public List<Class<? extends RetraceApiBinaryTest>> getCheckedInTestClasses() {
+    return CLASSES_FOR_BINARY_COMPATIBILITY;
+  }
+
+  @Override
+  public List<Class<? extends RetraceApiBinaryTest>> getPendingTestClasses() {
+    return CLASSES_PENDING_BINARY_COMPATIBILITY;
+  }
+
+  @Override
+  public List<Class<?>> getAdditionalClassesForTests() {
+    return ImmutableList.of(RetraceApiBinaryTest.class);
+  }
+
+  @Override
+  public List<String> getVmArgs() {
+    return ImmutableList.of();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestHelper.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestHelper.java
deleted file mode 100644
index 3bbb842..0000000
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestHelper.java
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.retrace.api;
-
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.Collectors;
-import com.android.tools.r8.TestRuntime.CfRuntime;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.transformers.ClassFileTransformer;
-import com.android.tools.r8.transformers.ClassFileTransformer.InnerClassPredicate;
-import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.ZipUtils;
-import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
-import com.google.common.collect.ImmutableList;
-import java.io.File;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import org.junit.rules.TemporaryFolder;
-
-public class RetraceApiTestHelper {
-
-  private static final String JUNIT_JAR = "junit-4.13-beta-2.jar";
-  private static final String HAMCREST = "hamcrest-core-1.3.jar";
-
-  public static List<Class<? extends RetraceApiBinaryTest>> CLASSES_FOR_BINARY_COMPATIBILITY =
-      ImmutableList.of(
-          RetraceApiEmptyTest.RetraceTest.class,
-          RetraceApiSourceFileTest.ApiTest.class,
-          RetraceApiSourceFileNotFoundTest.ApiTest.class,
-          RetraceApiInferSourceFileTest.ApiTest.class,
-          RetraceApiSynthesizedClassTest.ApiTest.class,
-          RetraceApiSynthesizedFieldTest.ApiTest.class,
-          RetraceApiSynthesizedMethodTest.ApiTest.class,
-          RetraceApiSynthesizedFrameTest.ApiTest.class,
-          RetraceApiSynthesizedInnerFrameTest.ApiTest.class,
-          RetraceApiUnknownJsonTest.ApiTest.class,
-          RetraceApiRewriteFrameInlineNpeTest.ApiTest.class);
-  public static List<Class<? extends RetraceApiBinaryTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
-      ImmutableList.of(RetraceApiRewriteFrameInlineNpeResidualTest.ApiTest.class);
-
-  public static void runJunitOnTests(
-      CfRuntime runtime,
-      Path r8Jar,
-      Class<? extends RetraceApiBinaryTest> clazz,
-      TemporaryFolder temp)
-      throws Exception {
-    assertTrue(testIsSpecifiedAsBinaryOrPending(clazz));
-    List<Class<? extends RetraceApiBinaryTest>> testClasses = ImmutableList.of(clazz);
-    runJunitOnTests(
-        runtime, r8Jar, generateJarForRetraceBinaryTests(temp, testClasses), testClasses);
-  }
-
-  public static void runJunitOnTests(
-      CfRuntime runtime,
-      Path r8Jar,
-      Path testJar,
-      Collection<Class<? extends RetraceApiBinaryTest>> tests)
-      throws Exception {
-    List<Path> classPaths = ImmutableList.of(getJunitDependency(), getHamcrest(), r8Jar, testJar);
-    List<String> args = new ArrayList<>();
-    args.add("org.junit.runner.JUnitCore");
-    tests.forEach(test -> args.add(test.getTypeName()));
-    ProcessResult processResult =
-        ToolHelper.runJava(runtime, classPaths, args.toArray(new String[0]));
-    assertEquals(processResult.toString(), 0, processResult.exitCode);
-    assertThat(processResult.stdout, containsString("OK (" + tests.size() + " test"));
-  }
-
-  private static Path getJunitDependency() {
-    String junitPath =
-        Arrays.stream(System.getProperty("java.class.path").split(File.pathSeparator))
-            .filter(cp -> cp.endsWith(JUNIT_JAR))
-            .collect(Collectors.toSingle());
-    return Paths.get(junitPath);
-  }
-
-  private static Path getHamcrest() {
-    String junitPath =
-        Arrays.stream(System.getProperty("java.class.path").split(File.pathSeparator))
-            .filter(cp -> cp.endsWith(HAMCREST))
-            .collect(Collectors.toSingle());
-    return Paths.get(junitPath);
-  }
-
-  public static Path generateJarForRetraceBinaryTests(
-      TemporaryFolder temp, Collection<Class<? extends RetraceApiBinaryTest>> classes)
-      throws Exception {
-    Path jar = File.createTempFile("retrace_api_tests", ".jar", temp.getRoot()).toPath();
-    ZipBuilder zipBuilder = ZipBuilder.builder(jar);
-    for (Class<? extends RetraceApiBinaryTest> retraceApiTest : classes) {
-      zipBuilder.addFilesRelative(
-          ToolHelper.getClassPathForTests(),
-          ToolHelper.getClassFilesForInnerClasses(retraceApiTest));
-      zipBuilder.addBytes(
-          ZipUtils.zipEntryNameForClass(retraceApiTest),
-          ClassFileTransformer.create(retraceApiTest)
-              .removeInnerClasses(
-                  InnerClassPredicate.onName(
-                      DescriptorUtils.getBinaryNameFromJavaType(retraceApiTest.getTypeName())))
-              .transform());
-    }
-    zipBuilder.addFilesRelative(
-        ToolHelper.getClassPathForTests(),
-        ToolHelper.getClassFileForTestClass(RetraceApiBinaryTest.class));
-    return zipBuilder.build();
-  }
-
-  public static Collection<Class<? extends RetraceApiBinaryTest>> getBinaryCompatibilityTests() {
-    return CLASSES_FOR_BINARY_COMPATIBILITY;
-  }
-
-  private static boolean testIsSpecifiedAsBinaryOrPending(Class<?> clazz) {
-    return CLASSES_FOR_BINARY_COMPATIBILITY.contains(clazz)
-        || CLASSES_PENDING_BINARY_COMPATIBILITY.contains(clazz);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithMultipleLineMappingsStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithMultipleLineMappingsStackTrace.java
index 1041ace..4015e0e 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithMultipleLineMappingsStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithMultipleLineMappingsStackTrace.java
@@ -38,9 +38,16 @@
   @Override
   public List<String> retraceVerboseStackTrace() {
     return Arrays.asList(
+        "There are 3 ambiguous stack traces.",
         "java.lang.IndexOutOfBoundsException",
         "\tat java.util.ArrayList.get(ArrayList.java:411)",
-        "\tat com.android.tools.r8.Internal.void foo(int)(Internal.java)");
+        "\tat com.android.tools.r8.Internal.void foo(int)(Internal.java:10)",
+        "<OR> java.lang.IndexOutOfBoundsException",
+        "\tat java.util.ArrayList.get(ArrayList.java:411)",
+        "\tat com.android.tools.r8.Internal.void foo(int)(Internal.java:11)",
+        "<OR> java.lang.IndexOutOfBoundsException",
+        "\tat java.util.ArrayList.get(ArrayList.java:411)",
+        "\tat com.android.tools.r8.Internal.void foo(int)(Internal.java:12)");
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureNonVerboseStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureStackTrace.java
similarity index 93%
rename from src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureNonVerboseStackTrace.java
rename to src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureStackTrace.java
index 7c0c0c2..212976f 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureNonVerboseStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureStackTrace.java
@@ -8,7 +8,7 @@
 import java.util.Arrays;
 import java.util.List;
 
-public class AmbiguousWithSignatureNonVerboseStackTrace implements StackTraceForTest {
+public class AmbiguousWithSignatureStackTrace implements StackTraceForTest {
 
   @Override
   public List<String> obfuscatedStackTrace() {
@@ -42,16 +42,16 @@
         "There are 4 ambiguous stack traces.",
         "java.lang.IndexOutOfBoundsException",
         "\tat java.util.ArrayList.get(ArrayList.java:411)",
-        "\tat com.android.tools.r8.Internal.boolean foo(int,int)(Internal.java)",
+        "\tat com.android.tools.r8.Internal.boolean foo(int,int)(Internal.java:13)",
         "<OR> java.lang.IndexOutOfBoundsException",
         "\tat java.util.ArrayList.get(ArrayList.java:411)",
-        "\tat com.android.tools.r8.Internal.void foo(int)(Internal.java)",
+        "\tat com.android.tools.r8.Internal.void foo(int)(Internal.java:10)",
         "<OR> java.lang.IndexOutOfBoundsException",
         "\tat java.util.ArrayList.get(ArrayList.java:411)",
-        "\tat com.android.tools.r8.Internal.void foo(int,boolean)(Internal.java)",
+        "\tat com.android.tools.r8.Internal.void foo(int,boolean)(Internal.java:12)",
         "<OR> java.lang.IndexOutOfBoundsException",
         "\tat java.util.ArrayList.get(ArrayList.java:411)",
-        "\tat com.android.tools.r8.Internal.void foo(int,int)(Internal.java)");
+        "\tat com.android.tools.r8.Internal.void foo(int,int)(Internal.java:11)");
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureVerboseStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureVerboseStackTrace.java
deleted file mode 100644
index a00b7bf..0000000
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/AmbiguousWithSignatureVerboseStackTrace.java
+++ /dev/null
@@ -1,61 +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.retrace.stacktraces;
-
-import com.android.tools.r8.utils.StringUtils;
-import java.util.Arrays;
-import java.util.List;
-
-public class AmbiguousWithSignatureVerboseStackTrace implements StackTraceForTest {
-
-  @Override
-  public List<String> obfuscatedStackTrace() {
-    return Arrays.asList(
-        "java.lang.IndexOutOfBoundsException",
-        "\tat java.util.ArrayList.get(ArrayList.java:411)",
-        "\tat com.android.tools.r8.Internal.zza(Unknown)");
-  }
-
-  @Override
-  public String mapping() {
-    return StringUtils.lines(
-        "com.android.tools.r8.Internal -> com.android.tools.r8.Internal:",
-        "  10:10:void foo(int):10:10 -> zza",
-        "  11:11:void foo(int,int):11:11 -> zza",
-        "  12:12:void foo(int,boolean):12:12 -> zza",
-        "  13:13:boolean foo(int,int):13:13 -> zza");
-  }
-
-  @Override
-  public List<String> retracedStackTrace() {
-    return Arrays.asList(
-        "java.lang.IndexOutOfBoundsException",
-        "\tat java.util.ArrayList.get(ArrayList.java:411)",
-        "\tat com.android.tools.r8.Internal.foo(Internal.java)");
-  }
-
-  @Override
-  public List<String> retraceVerboseStackTrace() {
-    return Arrays.asList(
-        "There are 4 ambiguous stack traces.",
-        "java.lang.IndexOutOfBoundsException",
-        "\tat java.util.ArrayList.get(ArrayList.java:411)",
-        "\tat com.android.tools.r8.Internal.boolean foo(int,int)(Internal.java)",
-        "<OR> java.lang.IndexOutOfBoundsException",
-        "\tat java.util.ArrayList.get(ArrayList.java:411)",
-        "\tat com.android.tools.r8.Internal.void foo(int)(Internal.java)",
-        "<OR> java.lang.IndexOutOfBoundsException",
-        "\tat java.util.ArrayList.get(ArrayList.java:411)",
-        "\tat com.android.tools.r8.Internal.void foo(int,boolean)(Internal.java)",
-        "<OR> java.lang.IndexOutOfBoundsException",
-        "\tat java.util.ArrayList.get(ArrayList.java:411)",
-        "\tat com.android.tools.r8.Internal.void foo(int,int)(Internal.java)");
-  }
-
-  @Override
-  public int expectedWarnings() {
-    return 0;
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/DifferentLineNumberSpanStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/DifferentLineNumberSpanStackTrace.java
index 16a2725..b4efedd 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/DifferentLineNumberSpanStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/DifferentLineNumberSpanStackTrace.java
@@ -21,24 +21,33 @@
   public String mapping() {
     return StringUtils.lines(
         "com.android.tools.r8.naming.retrace.Main -> a:",
-        "  1:1:void method1(java.lang.String):42:44 -> a");
+        "  void method1(java.lang.String):42:44 -> a");
   }
 
   @Override
   public List<String> retracedStackTrace() {
     return Arrays.asList(
         "Exception in thread \"main\" java.lang.NullPointerException",
-        // TODO(b/201042571): Should be ambiguous or have line number removed
-        "\tat com.android.tools.r8.naming.retrace.Main.method1(Main.java:42)");
+        "\tat com.android.tools.r8.naming.retrace.Main.method1(Main.java:42)",
+        "<OR> Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.tools.r8.naming.retrace.Main.method1(Main.java:43)",
+        "<OR> Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.tools.r8.naming.retrace.Main.method1(Main.java:44)");
   }
 
   @Override
   public List<String> retraceVerboseStackTrace() {
     return Arrays.asList(
+        "There are 3 ambiguous stack traces.",
         "Exception in thread \"main\" java.lang.NullPointerException",
         "\tat com.android.tools.r8.naming.retrace.Main.void"
-            // TODO(b/201042571): Should be ambiguous or have line number removed
-            + " method1(java.lang.String)(Main.java:42)");
+            + " method1(java.lang.String)(Main.java:42)",
+        "<OR> Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " method1(java.lang.String)(Main.java:43)",
+        "<OR> Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " method1(java.lang.String)(Main.java:44)");
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineInOutlineStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineInOutlineStackTrace.java
new file mode 100644
index 0000000..a231a0f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineInOutlineStackTrace.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class InlineInOutlineStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList("java.io.IOException: INVALID_SENDER", "\tat a.a(:2)", "\tat b.s(:27)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "# { id: 'com.android.tools.r8.mapping', version: 'experimental' }",
+        "outline.Class -> a:",
+        "  1:2:int some.inlinee():75:76 -> a",
+        "  1:2:int outline():0 -> a",
+        "# { 'id':'com.android.tools.r8.outline' }",
+        "some.Class -> b:",
+        "  1:1:void foo.bar.Baz.qux():42:42 -> s",
+        "  5:5:int foo.bar.baz.outlineCaller(int):98:98 -> s",
+        "  5:5:int outlineCaller(int):24 -> s",
+        "  27:27:int outlineCaller(int):0:0 -> s",
+        "# { 'id':'com.android.tools.r8.outlineCallsite', 'positions': { '1': 4, '2': 5 } }");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "java.io.IOException: INVALID_SENDER",
+        "\tat some.inlinee(some.java:76)",
+        "\tat foo.bar.baz.outlineCaller(baz.java:98)",
+        "\tat some.Class.outlineCaller(Class.java:24)");
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    return Arrays.asList(
+        "java.io.IOException: INVALID_SENDER",
+        "\tat some.int inlinee()(some.java:76)",
+        "\tat foo.bar.baz.int outlineCaller(int)(baz.java:98)",
+        "\tat some.Class.int outlineCaller(int)(Class.java:24)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleLinesNoLineNumberStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleLinesNoLineNumberStackTrace.java
index 6df2b28..a961d7b 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleLinesNoLineNumberStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleLinesNoLineNumberStackTrace.java
@@ -41,10 +41,13 @@
     return Arrays.asList(
         "There are 2 ambiguous stack traces.",
         "Exception in thread \"main\" java.lang.NullPointerException",
-        "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String[])(Main.java)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " main(java.lang.String[])(Main.java:153)",
         "<OR> Exception in thread \"main\" java.lang.NullPointerException",
-        "\tat com.android.tools.r8.naming.retrace.Main.void method1(java.lang.String)(Main.java)",
-        "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String[])(Main.java)");
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " method1(java.lang.String)(Main.java:42)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " main(java.lang.String[])(Main.java:28)");
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleOriginalLinesNoLineNumberStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleOriginalLinesNoLineNumberStackTrace.java
index d4688c9..a4b4fa3 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleOriginalLinesNoLineNumberStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/MultipleOriginalLinesNoLineNumberStackTrace.java
@@ -27,17 +27,19 @@
   public List<String> retracedStackTrace() {
     return Arrays.asList(
         "Exception in thread \"main\" java.lang.NullPointerException",
-        // TODO(b/201042571): Should not report a line number
-        "\tat com.android.tools.r8.naming.retrace.Main.method1(Main.java:42)");
+        "\tat com.android.tools.r8.naming.retrace.Main.method1(Main.java)");
   }
 
   @Override
   public List<String> retraceVerboseStackTrace() {
     return Arrays.asList(
+        "There are 2 ambiguous stack traces.",
         "Exception in thread \"main\" java.lang.NullPointerException",
         "\tat com.android.tools.r8.naming.retrace.Main.void"
-            // TODO(b/201042571): Should not report a line number
-            + " method1(java.lang.String)(Main.java:42)");
+            + " method1(java.lang.String)(Main.java:42)",
+        "<OR> Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " method1(java.lang.String)(Main.java:43)");
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscatedLineNumberWithOverrideTest.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscatedLineNumberWithOverrideTest.java
index 2e57740..273b002 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscatedLineNumberWithOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscatedLineNumberWithOverrideTest.java
@@ -37,23 +37,13 @@
     return Arrays.asList(
         "Exception in thread \"main\" java.lang.NullPointerException",
         "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java:3)",
-        "\tat com.android.tools.r8.naming.retrace.Main.overload1(Main.java:7)",
-        "\tat com.android.tools.r8.naming.retrace.Main.definedOverload(Main.java:7)",
+        "\tat com.android.tools.r8.naming.retrace.Main.overload1(Main.java)",
+        "\tat com.android.tools.r8.naming.retrace.Main.definedOverload(Main.java)",
         "\tat com.android.tools.r8.naming.retrace.Main.mainPC(Main.java:42)",
         "<OR> Exception in thread \"main\" java.lang.NullPointerException",
         "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java:3)",
-        "\tat com.android.tools.r8.naming.retrace.Main.overload1(Main.java:7)",
-        "\tat com.android.tools.r8.naming.retrace.Main.definedOverload(Main.java:11)",
-        "\tat com.android.tools.r8.naming.retrace.Main.mainPC(Main.java:42)",
-        "<OR> Exception in thread \"main\" java.lang.NullPointerException",
-        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java:3)",
-        "\tat com.android.tools.r8.naming.retrace.Main.overload2(Main.java:11)",
-        "\tat com.android.tools.r8.naming.retrace.Main.definedOverload(Main.java:7)",
-        "\tat com.android.tools.r8.naming.retrace.Main.mainPC(Main.java:42)",
-        "<OR> Exception in thread \"main\" java.lang.NullPointerException",
-        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java:3)",
-        "\tat com.android.tools.r8.naming.retrace.Main.overload2(Main.java:11)",
-        "\tat com.android.tools.r8.naming.retrace.Main.definedOverload(Main.java:11)",
+        "\tat com.android.tools.r8.naming.retrace.Main.overload2(Main.java)",
+        "\tat com.android.tools.r8.naming.retrace.Main.definedOverload(Main.java)",
         "\tat com.android.tools.r8.naming.retrace.Main.mainPC(Main.java:42)");
   }
 
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java
index 579541f..8ec36a8 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/NoObfuscationRangeMappingWithStackTrace.java
@@ -26,8 +26,8 @@
         "Exception in thread \"main\" java.lang.NullPointerException",
         "\tat com.android.tools.r8.naming.retrace.Main.foo(Main.java:1)",
         "\tat com.android.tools.r8.naming.retrace.Main.bar(Main.java:3)",
-        "\tat com.android.tools.r8.naming.retrace.Main.baz(Main.java:8)",
-        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java:7)");
+        "\tat com.android.tools.r8.naming.retrace.Main.baz(Main.java:0)",
+        "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java:0)");
   }
 
   @Override
@@ -36,8 +36,8 @@
         "Exception in thread \"main\" java.lang.NullPointerException",
         "\tat com.android.tools.r8.naming.retrace.Main.void foo(long)(Main.java:1)",
         "\tat com.android.tools.r8.naming.retrace.Main.void bar(int)(Main.java:3)",
-        "\tat com.android.tools.r8.naming.retrace.Main.void baz()(Main.java:8)",
-        "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String[])(Main.java:7)");
+        "\tat com.android.tools.r8.naming.retrace.Main.void baz()(Main.java:0)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String[])(Main.java:0)");
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/OutlineInOutlineStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/OutlineInOutlineStackTrace.java
new file mode 100644
index 0000000..ae8b52b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/OutlineInOutlineStackTrace.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class OutlineInOutlineStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "java.io.IOException: INVALID_SENDER", "\tat a.a(:4)", "\tat b.a(:6)", "\tat c.a(:28)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "# { id: 'com.android.tools.r8.mapping', version: 'experimental' }",
+        "outline1.Class -> a:",
+        "  4:4:int outline():0:0 -> a",
+        "# { 'id':'com.android.tools.r8.outline' }",
+        "outline2.Class -> b:",
+        "  6:6:int outline():0:0 -> a",
+        "# { 'id':'com.android.tools.r8.outlineCallsite', 'positions': { '4': 43 } }",
+        "  42:43:int outline():0:0 -> a",
+        "# { 'id':'com.android.tools.r8.outline' }",
+        "some.Class -> c:",
+        "  1:1:void foo.bar.Baz.qux():42:42 -> a",
+        "  10:11:int outlineCaller(int):98:98 -> a",
+        "  28:28:int outlineCaller(int):0:0 -> a",
+        "# { 'id':'com.android.tools.r8.outlineCallsite', 'positions': { '42': 10, '43': 11 } }");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "java.io.IOException: INVALID_SENDER", "\tat some.Class.outlineCaller(Class.java:98)");
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    return Arrays.asList(
+        "java.io.IOException: INVALID_SENDER",
+        "\tat some.Class.int outlineCaller(int)(Class.java:98)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/OutlineSimpleStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/OutlineSimpleStackTrace.java
new file mode 100644
index 0000000..ad0dddc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/OutlineSimpleStackTrace.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class OutlineSimpleStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList("java.io.IOException: INVALID_SENDER", "\tat a.a(:1)", "\tat b.s(:27)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "# { id: 'com.android.tools.r8.mapping', version: 'experimental' }",
+        "outline.Class -> a:",
+        "  1:2:int outline() -> a",
+        "# { 'id':'com.android.tools.r8.outline' }",
+        "some.Class -> b:",
+        "  1:1:void foo.bar.Baz.qux():42:42 -> s",
+        "  4:4:int outlineCaller(int):98:98 -> s",
+        "  27:27:int outlineCaller(int):0:0 -> s",
+        "# { 'id':'com.android.tools.r8.outlineCallsite', 'positions': { '1': 4, '2': 5 } }");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "java.io.IOException: INVALID_SENDER", "\tat some.Class.outlineCaller(Class.java:98)");
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    return Arrays.asList(
+        "java.io.IOException: INVALID_SENDER",
+        "\tat some.Class.int outlineCaller(int)(Class.java:98)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/OutlineWithInliningStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/OutlineWithInliningStackTrace.java
new file mode 100644
index 0000000..a8198a2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/OutlineWithInliningStackTrace.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class OutlineWithInliningStackTrace implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList("java.io.IOException: INVALID_SENDER", "\tat a.a(:2)", "\tat b.s(:27)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "# { id: 'com.android.tools.r8.mapping', version: 'experimental' }",
+        "outline.Class -> a:",
+        "  1:2:int outline():0 -> a",
+        "# { 'id':'com.android.tools.r8.outline' }",
+        "some.Class -> b:",
+        "  1:1:void foo.bar.Baz.qux():42:42 -> s",
+        "  5:5:int foo.bar.baz.outlineCaller(int):98:98 -> s",
+        "  5:5:int outlineCaller(int):24 -> s",
+        "  27:27:int outlineCaller(int):0:0 -> s",
+        "# { 'id':'com.android.tools.r8.outlineCallsite', 'positions': { '1': 4, '2': 5 } }");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "java.io.IOException: INVALID_SENDER",
+        "\tat foo.bar.baz.outlineCaller(baz.java:98)",
+        "\tat some.Class.outlineCaller(Class.java:24)");
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    return Arrays.asList(
+        "java.io.IOException: INVALID_SENDER",
+        "\tat foo.bar.baz.int outlineCaller(int)(baz.java:98)",
+        "\tat some.Class.int outlineCaller(int)(Class.java:24)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/OutsideLineRangeStackTraceTest.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/OutsideLineRangeStackTraceTest.java
new file mode 100644
index 0000000..81587cf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/OutsideLineRangeStackTraceTest.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class OutsideLineRangeStackTraceTest implements StackTraceForTest {
+
+  @Override
+  public List<String> obfuscatedStackTrace() {
+    return Arrays.asList(
+        "java.io.IOException: INVALID_SENDER",
+        "\tat a.a(:2)",
+        "\tat a.a(Unknown Source)",
+        "\tat b.a(:27)",
+        "\tat b.a(Unknown Source)");
+  }
+
+  @Override
+  public String mapping() {
+    return StringUtils.lines(
+        "some.other.Class -> a:",
+        "  void method1():42:42 -> a",
+        "some.Class -> b:",
+        "  1:3:void method2():11:13 -> a",
+        "  4:4:void method2():10:10 -> a");
+  }
+
+  @Override
+  public List<String> retracedStackTrace() {
+    return Arrays.asList(
+        "java.io.IOException: INVALID_SENDER",
+        "\tat some.other.Class.method1(Class.java:42)",
+        "\tat some.other.Class.method1(Class.java:42)",
+        "\tat some.Class.a(Class.java:27)",
+        "\tat some.Class.method2(Class.java)");
+  }
+
+  @Override
+  public List<String> retraceVerboseStackTrace() {
+    return Arrays.asList(
+        "There are 2 ambiguous stack traces.",
+        "java.io.IOException: INVALID_SENDER",
+        "\tat some.other.Class.void method1()(Class.java:42)",
+        "\tat some.other.Class.void method1()(Class.java:42)",
+        "\tat some.Class.a(Class.java:27)",
+        // TODO(b/202055473): Could emit range 11-13.
+        "\tat some.Class.void method2()(Class.java)",
+        "<OR> java.io.IOException: INVALID_SENDER",
+        "\tat some.other.Class.void method1()(Class.java:42)",
+        "\tat some.other.Class.void method1()(Class.java:42)",
+        "\tat some.Class.a(Class.java:27)",
+        "\tat some.Class.void method2()(Class.java:10)");
+  }
+
+  @Override
+  public int expectedWarnings() {
+    return 0;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/SingleLineNoLineNumberStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/SingleLineNoLineNumberStackTrace.java
index 8f6e183..c30fdbf 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/SingleLineNoLineNumberStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/SingleLineNoLineNumberStackTrace.java
@@ -26,7 +26,7 @@
         "com.android.tools.r8.naming.retrace.Main -> foo.a:",
         "    0:0:void method1(java.lang.String):42:42 -> a",
         "    0:0:void main(java.lang.String[]):28 -> a",
-        "    0:0:void method2(java.lang.String):42:48 -> b",
+        "    0:0:void method2(java.lang.String):42:44 -> b",
         "    0:0:void main2(java.lang.String[]):29 -> b",
         "    void method3(java.lang.String):72:72 -> c",
         "    void main3(java.lang.String[]):30 -> c",
@@ -39,23 +39,23 @@
         "Exception in thread \"main\" java.lang.NullPointerException",
         "\tat com.android.tools.r8.naming.retrace.Main.method1(Main.java:42)",
         "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java:28)",
-        "\tat com.android.tools.r8.naming.retrace.Main.method2(Main.java:42)",
-        "\tat com.android.tools.r8.naming.retrace.Main.main2(Main.java:29)",
-        "\tat com.android.tools.r8.naming.retrace.Main.main3(Main.java:30)",
+        "\tat com.android.tools.r8.naming.retrace.Main.method2(Main.java)",
+        "\tat com.android.tools.r8.naming.retrace.Main.main2(Main.java)",
+        "\tat com.android.tools.r8.naming.retrace.Main.main3(Main.java)",
         "\tat com.android.tools.r8.naming.retrace.Main.main4(Main.java:153)",
         "<OR> Exception in thread \"main\" java.lang.NullPointerException",
         "\tat com.android.tools.r8.naming.retrace.Main.method1(Main.java:42)",
         "\tat com.android.tools.r8.naming.retrace.Main.main(Main.java:28)",
-        "\tat com.android.tools.r8.naming.retrace.Main.method2(Main.java:42)",
-        "\tat com.android.tools.r8.naming.retrace.Main.main2(Main.java:29)",
-        "\tat com.android.tools.r8.naming.retrace.Main.method3(Main.java:72)",
+        "\tat com.android.tools.r8.naming.retrace.Main.method2(Main.java)",
+        "\tat com.android.tools.r8.naming.retrace.Main.main2(Main.java)",
+        "\tat com.android.tools.r8.naming.retrace.Main.method3(Main.java)",
         "\tat com.android.tools.r8.naming.retrace.Main.main4(Main.java:153)");
   }
 
   @Override
   public List<String> retraceVerboseStackTrace() {
     return Arrays.asList(
-        "There are 2 ambiguous stack traces.",
+        "There are 6 ambiguous stack traces.",
         "Exception in thread \"main\" java.lang.NullPointerException",
         "\tat com.android.tools.r8.naming.retrace.Main.void"
             + " method1(java.lang.String)(Main.java:42)",
@@ -79,6 +79,54 @@
         "\tat com.android.tools.r8.naming.retrace.Main.void"
             + " method3(java.lang.String)(Main.java:72)",
         "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " main4(java.lang.String[])(Main.java:153)",
+        "<OR> Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " method1(java.lang.String)(Main.java:42)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String[])(Main.java:28)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " method2(java.lang.String)(Main.java:43)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " main2(java.lang.String[])(Main.java:29)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " main3(java.lang.String[])(Main.java:30)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " main4(java.lang.String[])(Main.java:153)",
+        "<OR> Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " method1(java.lang.String)(Main.java:42)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String[])(Main.java:28)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " method2(java.lang.String)(Main.java:43)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " main2(java.lang.String[])(Main.java:29)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " method3(java.lang.String)(Main.java:72)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " main4(java.lang.String[])(Main.java:153)",
+        "<OR> Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " method1(java.lang.String)(Main.java:42)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String[])(Main.java:28)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " method2(java.lang.String)(Main.java:44)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " main2(java.lang.String[])(Main.java:29)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " main3(java.lang.String[])(Main.java:30)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " main4(java.lang.String[])(Main.java:153)",
+        "<OR> Exception in thread \"main\" java.lang.NullPointerException",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " method1(java.lang.String)(Main.java:42)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void main(java.lang.String[])(Main.java:28)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " method2(java.lang.String)(Main.java:44)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " main2(java.lang.String[])(Main.java:29)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
+            + " method3(java.lang.String)(Main.java:72)",
+        "\tat com.android.tools.r8.naming.retrace.Main.void"
             + " main4(java.lang.String[])(Main.java:153)");
   }
 
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index a38f6f2..5e37647 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -587,6 +587,18 @@
         });
   }
 
+  public ClassFileTransformer rewriteEnclosingMethod(
+      String newOwner, String newName, String newDescriptor) {
+    return addClassTransformer(
+        new ClassTransformer() {
+
+          @Override
+          public void visitOuterClass(String owner, String name, String descriptor) {
+            super.visitOuterClass(newOwner, newName, newDescriptor);
+          }
+        });
+  }
+
   public ClassFileTransformer removeMethods(MethodPredicate predicate) {
     return addClassTransformer(
         new ClassTransformer() {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 4e2b27d..cf9ce0a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.retrace.RetraceMethodResult;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.Retracer;
+import java.util.OptionalInt;
 
 public interface InstructionSubject {
 
@@ -163,11 +164,13 @@
   }
 
   default RetraceFrameResult retraceLinePosition(Retracer retracer) {
-    return retrace(retracer).narrowByPosition(RetraceStackTraceContext.empty(), getLineNumber());
+    return retrace(retracer)
+        .narrowByPosition(RetraceStackTraceContext.empty(), OptionalInt.of(getLineNumber()));
   }
 
   default RetraceFrameResult retracePcPosition(Retracer retracer, MethodSubject methodSubject) {
     return retrace(retracer)
-        .narrowByPosition(RetraceStackTraceContext.empty(), getOffset(methodSubject).offset);
+        .narrowByPosition(
+            RetraceStackTraceContext.empty(), OptionalInt.of(getOffset(methodSubject).offset));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 8b54ae2..7a78ed0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -8,7 +8,7 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import com.android.tools.r8.Collectors;
+import com.android.tools.r8.CollectorsUtils;
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.DiagnosticsMatcher;
 import com.android.tools.r8.errors.Unreachable;
@@ -586,7 +586,7 @@
     return new TypeSafeMatcher<RetraceFrameResult>() {
       @Override
       protected boolean matchesSafely(RetraceFrameResult item) {
-        RetraceFrameElement single = item.stream().collect(Collectors.toSingle());
+        RetraceFrameElement single = item.stream().collect(CollectorsUtils.toSingle());
         Box<LinePosition> currentPosition = new Box<>(startPosition);
         Box<Boolean> returnValue = new Box<>();
         single.stream()
@@ -627,7 +627,7 @@
     return new TypeSafeMatcher<RetraceFrameResult>() {
       @Override
       protected boolean matchesSafely(RetraceFrameResult item) {
-        RetraceFrameElement single = item.stream().collect(Collectors.toSingle());
+        RetraceFrameElement single = item.stream().collect(CollectorsUtils.toSingle());
         return !single.stream()
             .anyMatch(
                 frame -> {
diff --git a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1 b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
new file mode 100644
index 0000000..759844b
--- /dev/null
+++ b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
@@ -0,0 +1 @@
+8ddcb2b3cd52273413a538b22438e996e5c0dfcb
\ No newline at end of file
diff --git a/third_party/dart-sdk.tar.gz.sha1 b/third_party/dart-sdk.tar.gz.sha1
index f8b4a60..2c16136 100644
--- a/third_party/dart-sdk.tar.gz.sha1
+++ b/third_party/dart-sdk.tar.gz.sha1
@@ -1 +1 @@
-0f15c6a81827ce4979b6fcf6183ef35df335d9dc
\ No newline at end of file
+5dfd3e8a9eabac7c138a3cc80e6bbd8b7dafd5ae
\ No newline at end of file
diff --git a/third_party/retrace/binary_compatibility.tar.gz.sha1 b/third_party/retrace/binary_compatibility.tar.gz.sha1
index 6b09100..f81f5fc 100644
--- a/third_party/retrace/binary_compatibility.tar.gz.sha1
+++ b/third_party/retrace/binary_compatibility.tar.gz.sha1
@@ -1 +1 @@
-e7ea0fbb97ccfb2faa89b58d60f8b54f5bca0130
\ No newline at end of file
+239b7652dc8282715373a1871987c3919df029d6
\ No newline at end of file
diff --git a/tools/archive.py b/tools/archive.py
index 24e8e0a..5207e5c 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -145,8 +145,6 @@
     utils.D8,
     utils.R8LIB,
     utils.R8LIB_NO_DEPS,
-    utils.R8RETRACE,
-    utils.R8RETRACE_NO_DEPS,
     utils.LIBRARY_DESUGAR_CONVERSIONS,
     '-Pno_internal'
   ])
@@ -193,8 +191,6 @@
       utils.R8_FULL_EXCLUDE_DEPS_JAR,
       utils.R8LIB_EXCLUDE_DEPS_JAR,
       utils.R8LIB_EXCLUDE_DEPS_JAR + '.map',
-      utils.R8RETRACE_JAR,
-      utils.R8RETRACE_EXCLUDE_DEPS_JAR,
       utils.MAVEN_ZIP,
       utils.MAVEN_ZIP_LIB,
       utils.DESUGAR_CONFIGURATION,
diff --git a/tools/fmt-diff.py b/tools/fmt-diff.py
index 4e97f3a..fc08c80 100755
--- a/tools/fmt-diff.py
+++ b/tools/fmt-diff.py
@@ -22,7 +22,7 @@
   upstream = subprocess.check_output(['git', 'cl', 'upstream']).strip()
   git_diff_process = Popen(['git', 'diff', '-U0', upstream], stdout=PIPE)
   fmt_process = Popen(
-      ['python', GOOGLE_JAVA_FORMAT_DIFF, '-p1', '-i'],
+      [sys.executable, GOOGLE_JAVA_FORMAT_DIFF, '-p1', '-i'],
       stdin=git_diff_process.stdout)
   git_diff_process.stdout.close()
   fmt_process.communicate()
diff --git a/tools/r8_release.py b/tools/r8_release.py
index f9d14f9..a0ed999 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -363,7 +363,6 @@
       g4_open('src.jar')
       g4_open('lib.jar')
       g4_open('lib.jar.map')
-      g4_open('retrace_lib.jar')
       g4_open('desugar_jdk_libs_configuration.jar')
       download_file(options.version, 'r8-full-exclude-deps.jar', 'full.jar')
       download_file(options.version, 'r8-src.jar', 'src.jar')
@@ -372,7 +371,6 @@
           options.version, 'r8lib-exclude-deps.jar.map', 'lib.jar.map')
       download_file(options.version, 'desugar_jdk_libs_configuration.jar',
                     'desugar_jdk_libs_configuration.jar')
-      download_file(options.version, 'r8retrace-exclude-deps.jar', 'retrace_lib.jar')
       g4_open('METADATA')
       sed(r'[1-9]\.[0-9]{1,2}\.[0-9]{1,3}-dev',
           options.version,
diff --git a/tools/test.py b/tools/test.py
index 6f27a56..071036c 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -318,7 +318,6 @@
     # Force gradle to build a version of r8lib without dependencies for
     # BootstrapCurrentEqualityTest.
     gradle_args.append('R8LibNoDeps')
-    gradle_args.append('R8Retrace')
   if options.r8lib_no_deps:
     gradle_args.append('-Pr8lib_no_deps')
   if options.worktree:
diff --git a/tools/utils.py b/tools/utils.py
index 641e352..53aac35 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -41,8 +41,6 @@
 R8 = 'r8'
 R8LIB = 'r8lib'
 R8LIB_NO_DEPS = 'r8LibNoDeps'
-R8RETRACE = 'R8Retrace'
-R8RETRACE_NO_DEPS = 'R8RetraceNoDeps'
 R8_SRC = 'sourceJar'
 LIBRARY_DESUGAR_CONVERSIONS = 'buildLibraryDesugarConversions'
 
@@ -54,8 +52,6 @@
 R8_SRC_JAR = os.path.join(LIBS, 'r8-src.jar')
 R8LIB_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8lib-exclude-deps.jar')
 R8_FULL_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8-full-exclude-deps.jar')
-R8RETRACE_JAR = os.path.join(LIBS, 'r8retrace.jar')
-R8RETRACE_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8retrace-exclude-deps.jar')
 MAVEN_ZIP = os.path.join(LIBS, 'r8.zip')
 MAVEN_ZIP_LIB = os.path.join(LIBS, 'r8lib.zip')
 LIBRARY_DESUGAR_CONVERSIONS_ZIP = os.path.join(LIBS, 'library_desugar_conversions.zip')