Revert "Reland "Fix Kotlin inline retrace support and SMAP parsing.""

This reverts commit 48b808dd9ba6940019bb5c50dfdddd9f378d8620.

Reason for revert: OOM on CI

Failure Link: https://ci.chromium.org/ui/p/r8/builders/ci/linux-android-12/2189

Original change's description:
> Reland "Fix Kotlin inline retrace support and SMAP parsing."
>
> This now only finds references for live classes.
>
> This is a reland of commit a412c4aba9a651046e5c726b872865f8ed8c737a
>
> Original change's description:
> > Fix Kotlin inline retrace support and SMAP parsing.
> >
> > Kotlin code can contain inlined methods which is annotated with
> > <file>:<line>-<line> to describe where it was inlined from. This means
> > that we have to resolve such references to actual methods in the source
> > to preserve them in the output mapping.
> >
> > In R8 partial, an inlined reference might point into D8 code, which
> > means that both branches need "full" and upfront info to resolve them.
> >
> > This CL stores where methods start and end for files referenced by
> > inline annotations if output mapping files are enabled. This data does
> > not need maintenance since it should always refer to the input program.
> >
> > I also fixed the handling of overlapping ranges in the SMAP parser to
> > comply with specs.
> >
> > Fixes: b/391785584
> > Change-Id: I77759eeddef82ec1960df11aa75c6bf5c22e67e0
>
> Change-Id: I5a623ce9984cd3489aa91b49fd066a28a76aaf80

TBR=christofferqa@google.com,r8-scoped@luci-project-accounts.iam.gserviceaccount.com,android-build-ayeaye@system.gserviceaccount.com,jonathanlist@google.com

No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Change-Id: I48f0a476a5a889f1d775ebf2ce17ffd6a20ac847
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index cf89c64..f169044 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -35,7 +35,6 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.keepanno.annotations.KeepForApi;
-import com.android.tools.r8.kotlin.KotlinInlineMethodMap;
 import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.RecordRewritingNamingLens;
@@ -256,8 +255,6 @@
       if (options.testing.enableD8MetaInfServicesPassThrough) {
         appView.setAppServices(AppServices.builder(appView).build());
       }
-      appView.setKotlinInlineMethodMap(
-          KotlinInlineMethodMap.createForD8(appView, executor, timing));
       timing.end();
       new PrimaryD8L8IRConverter(appView, timing).convert(appView, executor);
       timing.begin("Post conversion");
@@ -334,7 +331,7 @@
       timing.end(); // post-converter
 
       reportSyntheticInformation(appView);
-      writeApplication(appView, marker, executor, timing);
+      writeApplication(appView, inputApp, marker, executor, timing);
 
       options.printWarnings();
     } catch (ExecutionException e) {
@@ -400,6 +397,7 @@
 
   private static void writeApplication(
       AppView<AppInfo> appView,
+      AndroidApp inputApp,
       Marker marker,
       ExecutorService executor,
       Timing timing)
@@ -409,9 +407,9 @@
       new R8PartialApplicationWriter(appView).write(executor);
     } else if (options.isGeneratingClassFiles()) {
       new CfApplicationWriter(appView, marker)
-          .write(options.getClassFileConsumer(), executor, timing);
+          .write(options.getClassFileConsumer(), executor, timing, inputApp);
     } else {
-      ApplicationWriter.create(appView, marker).write(executor, timing);
+      ApplicationWriter.create(appView, marker).write(executor, timing, inputApp);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
index ca321a8..0ae18a8 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
@@ -144,11 +144,11 @@
               assert GlobalSyntheticsGeneratorVerifier.verifyExpectedClassesArePresent(appView);
               if (options.isGeneratingDex()) {
                 ApplicationWriter.create(appView, options.getMarker())
-                    .write(executorService, timing);
+                    .write(executorService, timing, app);
               } else {
                 assert options.isGeneratingClassFiles();
                 new CfApplicationWriter(appView, options.getMarker())
-                    .write(options.getClassFileConsumer(), executorService, timing);
+                    .write(options.getClassFileConsumer(), executorService, timing, app);
               }
             } catch (ExecutionException e) {
               throw unwrapExecutionException(e);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 91cb423..b67a71c 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -64,7 +64,6 @@
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.keepanno.annotations.KeepForApi;
 import com.android.tools.r8.keepanno.ast.KeepDeclaration;
-import com.android.tools.r8.kotlin.KotlinInlineMethodMap;
 import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
 import com.android.tools.r8.kotlin.KotlinMetadataUtils;
 import com.android.tools.r8.naming.IdentifierMinifier;
@@ -217,7 +216,8 @@
         command.getReporter(), () -> runInternal(app, options, executor));
   }
 
-  static void writeApplication(AppView<?> appView, ExecutorService executorService, Timing timing)
+  static void writeApplication(
+      AppView<?> appView, AndroidApp inputApp, ExecutorService executorService, Timing timing)
       throws ExecutionException {
     InternalOptions options = appView.options();
     InspectorImpl.runInspections(options.outputInspections, appView.appInfo().classes());
@@ -226,9 +226,9 @@
       assert marker != null;
       if (options.isGeneratingClassFiles()) {
         new CfApplicationWriter(appView, marker)
-            .write(options.getClassFileConsumer(), executorService, timing);
+            .write(options.getClassFileConsumer(), executorService, timing, inputApp);
       } else {
-        ApplicationWriter.create(appView, marker).write(executorService, timing);
+        ApplicationWriter.create(appView, marker).write(executorService, timing, inputApp);
       }
     } catch (IOException e) {
       throw new RuntimeException("Cannot write application", e);
@@ -428,9 +428,6 @@
         ExceptionUtils.withFinishedResourceHandler(options.reporter, options.proguardSeedsConsumer);
       }
 
-      appView.setKotlinInlineMethodMap(
-          KotlinInlineMethodMap.createForR8(appViewWithLiveness, executorService, timing));
-
       if (options.isShrinking()) {
         // Mark dead proto extensions fields as neither being read nor written. This step must
         // run prior to the tree pruner.
@@ -960,7 +957,7 @@
 
       // Generate the resulting application resources.
       writeKeepDeclarationsToConfigurationConsumer(keepDeclarations);
-      writeApplication(appView, executorService, timing);
+      writeApplication(appView, inputApp, executorService, timing);
       ResourceWriter.legacyWriteResources(appView, dexFileContent);
 
       assert appView.getDontWarnConfiguration().validate(options);
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 55d55f3..5f386f2 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -50,6 +50,7 @@
 import com.android.tools.r8.profile.startup.StartupCompleteness;
 import com.android.tools.r8.profile.startup.profile.StartupProfile;
 import com.android.tools.r8.shaking.MainDexInfo;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.DexVersion;
 import com.android.tools.r8.utils.ExceptionUtils;
@@ -260,6 +261,16 @@
     }
   }
 
+  private boolean willComputeProguardMap() {
+    return options.hasMappingFileSupport();
+  }
+
+  /** Writer that never needs the input app to deal with mapping info for kotlin. */
+  public void write(ExecutorService executorService, Timing timing)
+      throws IOException, ExecutionException {
+    assert !willComputeProguardMap();
+    write(executorService, timing, null);
+  }
 
   protected void writeVirtualFiles(
       ExecutorService executorService,
@@ -292,7 +303,7 @@
     }
   }
 
-  public void write(ExecutorService executorService, Timing timing)
+  public void write(ExecutorService executorService, Timing timing, AndroidApp inputApp)
       throws IOException, ExecutionException {
     timing.begin("DexApplication.write");
 
@@ -338,12 +349,13 @@
 
       // Now that the instruction offsets in each code object are fixed, compute the mapping file
       // content.
-      if (options.shouldOutputMappingFile()) {
+      if (willComputeProguardMap()) {
         // TODO(b/220999985): Refactor line number optimization to be per file and thread it above.
         DebugRepresentationPredicate representation =
             DebugRepresentation.fromFiles(virtualFiles, options);
         mapSupplierResult =
-            runAndWriteMap(appView, executorService, timing, originalSourceFiles, representation);
+            runAndWriteMap(
+                inputApp, appView, executorService, timing, originalSourceFiles, representation);
       } else if (options.convertPcBasedDebugInfoToNative) {
         convertPcBasedDebugInfoToNative(appView, executorService, timing);
       }
@@ -421,7 +433,7 @@
     DexItemFactory factory = appView.dexItemFactory();
     currentMarker.ifPresent(
         marker -> {
-          if (options.shouldOutputMappingFile()) {
+          if (willComputeProguardMap()) {
             lazyDexStrings.add(
                 new LazyDexString() {
 
@@ -443,7 +455,7 @@
     if (options.sourceFileProvider == null) {
       return OriginalSourceFiles.fromClasses();
     }
-    if (!options.shouldOutputMappingFile()) {
+    if (!willComputeProguardMap()) {
       rewriteSourceFile(appView.appInfo().classes(), null);
       return OriginalSourceFiles.unreachable();
     }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index e30476f..7c09caf 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -212,10 +212,6 @@
         .toSingleClassWithProgramOverLibrary();
   }
 
-  public final DexProgramClass definitionForProgramTypeWithoutExistenceAssert(DexType type) {
-    return DexProgramClass.asProgramClassOrNull(definitionForWithoutExistenceAssert(type));
-  }
-
   public final boolean hasDefinitionForWithoutExistenceAssert(DexType type) {
     return definitionForWithoutExistenceAssert(type) != null;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index be83481..92d95f3 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -39,7 +39,6 @@
 import com.android.tools.r8.ir.optimize.library.LibraryMethodSideEffectModelCollection;
 import com.android.tools.r8.ir.optimize.outliner.bottomup.BottomUpOutliner;
 import com.android.tools.r8.ir.optimize.unsafe.SyntheticUnsafeClass;
-import com.android.tools.r8.kotlin.KotlinInlineMethodMap;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.SeedMapper;
 import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
@@ -160,7 +159,6 @@
   private Set<DexMethod> cfByteCodePassThrough = ImmutableSet.of();
   private final Map<DexType, DexValueString> sourceDebugExtensions = new IdentityHashMap<>();
   private final Map<DexType, String> sourceFileForPrunedTypes = new IdentityHashMap<>();
-  private KotlinInlineMethodMap kotlinInlineMethodMap = null;
 
   // Types.
   private TypeElementFactory typeElementFactory = new TypeElementFactory();
@@ -521,14 +519,6 @@
     return sourceDebugExtensions.get(clazz.type);
   }
 
-  public KotlinInlineMethodMap getKotlinInlineMethodMap() {
-    return kotlinInlineMethodMap;
-  }
-
-  public void setKotlinInlineMethodMap(KotlinInlineMethodMap kotlinInlineMethodMap) {
-    this.kotlinInlineMethodMap = kotlinInlineMethodMap;
-  }
-
   @Override
   public ClassResolutionResult contextIndependentDefinitionForWithResolutionResult(DexType type) {
     return appInfo().contextIndependentDefinitionForWithResolutionResult(type);
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index c3acc1b..513b41b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -1,6 +1,9 @@
 // Copyright (c) 2016, 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.
+// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.DataResourceProvider;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 2ed7223..b7c90de 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -2695,7 +2695,7 @@
                       .setCode(
                           methodSig -> {
                             Code code = generateTemplateMethod(appView.dexItemFactory(), methodSig);
-                            if (appView.options().shouldOutputMappingFile()) {
+                            if (appView.options().hasMappingFileSupport()) {
                               return code.getCodeAsInlining(
                                   methodSig, true, member, false, appView.dexItemFactory());
                             }
@@ -2761,7 +2761,7 @@
                               methodSig -> {
                                 Code code =
                                     factoryHelper.create(appView.dexItemFactory(), methodSig);
-                                if (appView.options().shouldOutputMappingFile()) {
+                                if (appView.options().hasMappingFileSupport()) {
                                   return code.getCodeAsInlining(
                                       methodSig, true, member, false, appView.dexItemFactory());
                                 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index 4851feb..35152b4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -264,7 +264,7 @@
       IntBox locals = new IntBox();
       IntBox stack = new IntBox();
       Position preamble =
-          !lambda.appView.options().shouldOutputMappingFile()
+          !lambda.appView.options().hasMappingFileSupport()
               ? null
               : SyntheticPosition.builder()
                   .setMethod(mainMethod)
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index d75b651..2dad2de 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -299,7 +299,7 @@
     CfDesugaringInfoImpl desugaringInfo = new CfDesugaringInfoImpl(cfCode.bytecodeSizeUpperBound());
 
     Box<Position> currentPosition = new Box<>();
-    boolean maintainPositionForInlineInfo = appView.options().shouldOutputMappingFile();
+    boolean maintainPositionForInlineInfo = appView.options().hasMappingFileSupport();
     if (maintainPositionForInlineInfo) {
       currentPosition.set(cfCode.getPreamblePosition());
       if (!currentPosition.isSet()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java
index 08aa1dd..cb522b7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java
@@ -271,7 +271,7 @@
                         .setCode(
                             methodSig -> {
                               CfCode code = cfCodeGen.apply(methodSig);
-                              if (appView.options().shouldOutputMappingFile()) {
+                              if (appView.options().hasMappingFileSupport()) {
                                 return code.getCodeAsInlining(
                                     methodSig, true, context.getReference(), false, factory);
                               }
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index e6f4af4..b63774d 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
 import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapSupplierResult;
 import com.android.tools.r8.threading.TaskCollection;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramConsumer.InternalGlobalSyntheticsCfConsumer;
 import com.android.tools.r8.utils.InternalOptions;
@@ -51,8 +52,19 @@
 
   public void write(ClassFileConsumer consumer, ExecutorService executorService, Timing timing)
       throws ExecutionException {
+    assert !options.hasMappingFileSupport();
+    write(consumer, executorService, timing, null);
+  }
+
+  public void write(
+      ClassFileConsumer consumer,
+      ExecutorService executorService,
+      Timing timing,
+      AndroidApp inputApp)
+      throws ExecutionException {
     timing.time(
-        "CfApplicationWriter.write", () -> writeApplication(consumer, executorService, timing));
+        "CfApplicationWriter.write",
+        () -> writeApplication(inputApp, consumer, executorService, timing));
   }
 
   private boolean includeMarker(Marker marker) {
@@ -67,6 +79,7 @@
   }
 
   private void writeApplication(
+      AndroidApp inputApp,
       ClassFileConsumer consumer,
       ExecutorService executorService,
       Timing timing)
@@ -75,10 +88,11 @@
 
     ProguardMapSupplierResult mapSupplierResult = ProguardMapSupplierResult.createEmpty();
     try {
-      if (options.shouldOutputMappingFile()) {
+      if (options.hasMappingFileSupport()) {
         assert marker.isPresent();
         mapSupplierResult =
             runAndWriteMap(
+                inputApp,
                 appView,
                 executorService,
                 timing,
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInlineMethodMap.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInlineMethodMap.java
deleted file mode 100644
index 8fa6444..0000000
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInlineMethodMap.java
+++ /dev/null
@@ -1,257 +0,0 @@
-// Copyright (c) 2026, 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.kotlin;
-
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.internal.IntBox;
-import com.android.tools.r8.utils.internal.collections.ImmutableDisjointIntRangeMap;
-import com.android.tools.r8.utils.timing.Timing;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.function.Function;
-import java.util.function.Predicate;
-
-/**
- * Supports querying {@code (file, lineStart, lineEnd)} pairs for which method that refers to in the
- * input. This is only collected for files which are pointed to by Kotlin inline annotations. It is
- * also only collected if a mapping file will be generated.
- *
- * <p>Context: When Kotlin compiles code that inlines functions, it adds a {@code
- * @kotlin.jvm.internal.SourceDebugExtension} (SMAP) attribute to the caller class mapping compiler
- * line numbers back to source lines in the inlinee source file. However, SMAP data only contains
- * the inlinee file path and line numbers, not the method signatures of the inlined functions.
- * During mapping file generation (e.g. in {@link
- * com.android.tools.r8.utils.positions.LineNumberOptimizer}), R8 reconstructs inlined frames by
- * looking up which source method corresponded to each inlined line.
- */
-public class KotlinInlineMethodMap {
-
-  private static final KotlinInlineMethodMap EMPTY =
-      new KotlinInlineMethodMap(Collections.emptyMap());
-
-  private final Map<DexType, ImmutableDisjointIntRangeMap<DexMethod>> inlineMethodMap;
-
-  private KotlinInlineMethodMap(
-      Map<DexType, ImmutableDisjointIntRangeMap<DexMethod>> inlineMethodMap) {
-    this.inlineMethodMap = inlineMethodMap;
-  }
-
-  public static KotlinInlineMethodMap empty() {
-    return EMPTY;
-  }
-
-  public DexMethod lookup(DexType type, int line) {
-    ImmutableDisjointIntRangeMap<DexMethod> classLines = inlineMethodMap.get(type);
-    return classLines != null ? classLines.get(line) : null;
-  }
-
-  public static KotlinInlineMethodMap createForR8(
-      AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
-      throws ExecutionException {
-    InternalOptions options = appView.options();
-    if (!options.shouldOutputMappingFile()) {
-      return null;
-    }
-    return timing.time(
-        "Extract kotlin inline method map",
-        () -> {
-          AppInfoWithLiveness appInfo = appView.appInfo();
-          Function<DexType, DexProgramClass> classProvider =
-              type -> {
-                // Code might validly be inlined from non-present sources.
-                DexProgramClass clazz =
-                    appInfo.definitionForProgramTypeWithoutExistenceAssert(type);
-                if (clazz != null) {
-                  return clazz;
-                }
-                if (options.getR8PartialR8SubCompilationOptions() != null) {
-                  return options.getR8PartialR8SubCompilationOptions().getDexingOutputClass(type);
-                }
-                return null;
-              };
-          return createInternal(
-              appInfo.classes(),
-              appInfo::isLiveProgramClass,
-              classProvider,
-              options,
-              appView.dexItemFactory(),
-              executorService);
-        });
-  }
-
-  public static KotlinInlineMethodMap createForD8(
-      AppView<AppInfo> appView, ExecutorService executorService, Timing timing)
-      throws ExecutionException {
-    InternalOptions options = appView.options();
-    if (!options.shouldOutputMappingFile()) {
-      return null;
-    }
-    return timing.time(
-        "Extract kotlin inline method map",
-        () ->
-            createInternal(
-                appView.appInfo().classes(),
-                clazz -> true,
-                appView::definitionForProgramType,
-                options,
-                appView.dexItemFactory(),
-                executorService));
-  }
-
-  private static KotlinInlineMethodMap createInternal(
-      Collection<DexProgramClass> classes,
-      Predicate<DexProgramClass> isLive,
-      Function<DexType, DexProgramClass> classProvider,
-      InternalOptions options,
-      DexItemFactory factory,
-      ExecutorService executorService)
-      throws ExecutionException {
-    Set<DexType> inlineeClasses =
-        findInlineReferences(classes, isLive, options, factory, executorService);
-    if (inlineeClasses.isEmpty()) {
-      return empty();
-    }
-    var classLines = collectClassLines(inlineeClasses, classProvider, options, executorService);
-    return new KotlinInlineMethodMap(classLines);
-  }
-
-  private static Map<DexType, ImmutableDisjointIntRangeMap<DexMethod>> collectClassLines(
-      Set<DexType> inlineeClasses,
-      Function<DexType, DexProgramClass> classProvider,
-      InternalOptions options,
-      ExecutorService executorService)
-      throws ExecutionException {
-    Collection<Entry<DexType, ImmutableDisjointIntRangeMap<DexMethod>>> classLines =
-        ThreadUtils.processItemsWithResultsThatMatches(
-            inlineeClasses,
-            type -> {
-              DexProgramClass clazz = classProvider.apply(type);
-              return clazz != null ? computeClassLines(clazz) : null;
-            },
-            Objects::nonNull,
-            options.getThreadingModule(),
-            executorService);
-    return ImmutableMap.copyOf(classLines);
-  }
-
-  private static Set<DexType> findInlineReferences(
-      Collection<DexProgramClass> classes,
-      Predicate<DexProgramClass> isLive,
-      InternalOptions options,
-      DexItemFactory factory,
-      ExecutorService executorService)
-      throws ExecutionException {
-    Set<DexType> inlineeClasses = ConcurrentHashMap.newKeySet();
-    ThreadUtils.processItemsWithFilterPreprocessing(
-        classes,
-        clazz -> {
-          if (!isLive.test(clazz)) {
-            return null;
-          }
-          DexAnnotation annotation =
-              clazz.annotations().getFirstMatching(factory.annotationSourceDebugExtension);
-          if (annotation == null || annotation.annotation.elements.length == 0) {
-            return null;
-          }
-          DexValue value = annotation.annotation.elements[0].value;
-          if (!value.isDexValueString()) {
-            return null;
-          }
-          return value.asDexValueString();
-        },
-        (clazz, value) -> {
-          var smap = KotlinSourceDebugExtensionParser.parse(value);
-          if (smap == null) {
-            return;
-          }
-          smap.getInlineePositions()
-              .forEach(
-                  inlineePosition -> {
-                    String internalName = inlineePosition.getSource().getPath();
-                    String descriptor =
-                        DescriptorUtils.getDescriptorFromClassInternalName(internalName);
-                    DexType inlineeType = factory.createType(descriptor);
-                    inlineeClasses.add(inlineeType);
-                  });
-        },
-        options,
-        executorService);
-    return ImmutableSet.copyOf(inlineeClasses);
-  }
-
-  private static class MethodInterval {
-    final int min;
-    final int max;
-    final DexMethod method;
-
-    MethodInterval(int min, int max, DexMethod method) {
-      this.min = min;
-      this.max = max;
-      this.method = method;
-    }
-  }
-
-  private static Entry<DexType, ImmutableDisjointIntRangeMap<DexMethod>> computeClassLines(
-      DexProgramClass clazz) {
-    var builder = ImmutableDisjointIntRangeMap.<DexMethod>builder();
-    for (DexEncodedMethod method : clazz.methods()) {
-      MethodInterval interval = extractMethodInterval(method);
-      if (interval != null) {
-        try {
-          builder.add(interval.min, interval.max, interval.method);
-        } catch (IllegalArgumentException ignored) {
-          // Kotlin inline annotations are supported on a best-effort basis, ignore this entry.
-        }
-      }
-    }
-    ImmutableDisjointIntRangeMap<DexMethod> rangeMap = builder.build();
-    return rangeMap.isEmpty() ? null : Map.entry(clazz.getType(), rangeMap);
-  }
-
-  private static MethodInterval extractMethodInterval(DexEncodedMethod method) {
-    if (!method.hasCode()) {
-      return null;
-    }
-    Code code = method.getCode();
-    if (code.isLazyCfCode()) {
-      // Unwrap LazyCfCode since it does not override forEachPosition.
-      code = code.asCfCode();
-    }
-    IntBox min = new IntBox(Integer.MAX_VALUE);
-    IntBox max = new IntBox(Integer.MIN_VALUE);
-    code.forEachPosition(
-        method.getReference(),
-        method.isD8R8Synthesized(),
-        position -> {
-          int line = position.getLine();
-          min.setMin(line);
-          max.setMax(line);
-        });
-    return min.get() != Integer.MAX_VALUE
-        ? new MethodInterval(min.get(), max.get(), method.getReference())
-        : null;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
index 398cc8b..e2efac1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.naming.Range;
 import com.android.tools.r8.utils.internal.StringUtils;
 import com.android.tools.r8.utils.internal.ThrowingConsumer;
-import com.android.tools.r8.utils.internal.collections.ImmutableDisjointIntRangeMap;
+import com.android.tools.r8.utils.internal.collections.SegmentTree;
 import java.io.BufferedReader;
 import java.io.Closeable;
 import java.io.IOException;
@@ -172,7 +172,7 @@
         if (reader.isEOF()) {
           assert nextLine == null;
           return new KotlinSourceDebugExtensionParserResult(
-              inlineePositions.build(), calleePositions.build());
+              inlineePositions.segmentTree, calleePositions.segmentTree);
         }
         if (!nextLine.equals(SMAP_SECTION_KOTLIN_DEBUG_START)) {
           return null;
@@ -188,7 +188,7 @@
             "Unexpected EOF when parsing SMAP debug entries");
       }
       return new KotlinSourceDebugExtensionParserResult(
-          inlineePositions.build(), calleePositions.build());
+          inlineePositions.segmentTree, calleePositions.segmentTree);
     } catch (IOException | KotlinSourceDebugExtensionParserException e) {
       return null;
     }
@@ -296,23 +296,9 @@
         throw new KotlinSourceDebugExtensionParserException(
             "Could not find file with index " + fileIndex);
       }
-      // https://jakarta.ee/specifications/debugging/2.0/jdsol-spec-2.0.pdf
-      // > Note that multiple LineInfo may map multiple input source lines to a single output source
-      // > line, when such a LineSection is being used to map output source lines to input source
-      // > lines, a first matching LineInfo rule applies.
-      //
-      // So entries should only add intervals where previous entries did not.
-      // `addUncoveredSubRanges` splits the interval into the sub-intervals not already covered.
-      builder.positions.addUncoveredSubRanges(
-          target,
-          target + (size - 1),
-          (subStart, subEnd) -> {
-            int delta = subStart - target;
-            int subSize = subEnd - subStart + 1;
-            int subOriginalStart = originalStart + delta;
-            Range subRange = new Range(subOriginalStart, subOriginalStart + (subSize - 1));
-            return new Position(thisFileSource, subRange);
-          });
+      Range range = new Range(originalStart, originalStart + (size - 1));
+      Position position = new Position(thisFileSource, range);
+      builder.segmentTree.add(target, target + (size - 1), position);
     } catch (NumberFormatException e) {
       throw new KotlinSourceDebugExtensionParserException("Could not convert position to number");
     }
@@ -320,30 +306,25 @@
 
   public static class KotlinSourceDebugExtensionParserResult {
 
-    private final ImmutableDisjointIntRangeMap<Position> inlineePositions;
-    private final ImmutableDisjointIntRangeMap<Position> calleePositions;
+    private final SegmentTree<Position> inlineePositions;
+    private final SegmentTree<Position> calleePositions;
 
     public KotlinSourceDebugExtensionParserResult(
-        ImmutableDisjointIntRangeMap<Position> inlineePositions,
-        ImmutableDisjointIntRangeMap<Position> calleePositions) {
+        SegmentTree<Position> inlineePositions, SegmentTree<Position> calleePositions) {
       this.inlineePositions = inlineePositions;
       this.calleePositions = calleePositions;
     }
 
-    public ImmutableDisjointIntRangeMap<Position> getInlineePositions() {
+    public SegmentTree<Position> getInlineePositions() {
       return inlineePositions;
     }
 
-    public ImmutableDisjointIntRangeMap.Entry<Position> lookupInlinedPositionEntry(int point) {
-      return inlineePositions.getEntry(point);
+    public Map.Entry<Integer, Position> lookupInlinedPosition(int point) {
+      return inlineePositions.findEntry(point);
     }
 
-    public Position lookupInlinedPosition(int point) {
-      return inlineePositions.get(point);
-    }
-
-    public Position lookupCalleePosition(int point) {
-      return calleePositions.get(point);
+    public Map.Entry<Integer, Position> lookupCalleePosition(int point) {
+      return calleePositions.findEntry(point);
     }
 
     public int inlinePositionsCount() {
@@ -353,13 +334,8 @@
 
   public static class StratumBuilder {
 
-    private final ImmutableDisjointIntRangeMap.Builder<Position> positions =
-        ImmutableDisjointIntRangeMap.builder();
+    SegmentTree<Position> segmentTree = new SegmentTree<>(false);
     Map<Integer, Source> files = new HashMap<>();
-
-    public ImmutableDisjointIntRangeMap<Position> build() {
-      return positions.build();
-    }
   }
 
   public static class Source {
diff --git a/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java b/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java
new file mode 100644
index 0000000..7a25330
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java
@@ -0,0 +1,208 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import static com.android.tools.r8.utils.internal.FileUtils.CLASS_EXTENSION;
+
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.KotlinSourceDebugExtensionParserResult;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.ArchiveResourceProvider.ArchiveResourceProviderHelper;
+import com.android.tools.r8.utils.internal.StreamUtils;
+import com.android.tools.r8.utils.timing.Timing;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+
+public class CfLineToMethodMapper {
+
+  private static final String NAME_DESCRIPTOR_SEPARATOR = ";;";
+
+  private final Map<String, Int2ObjectMap<String>> sourceMethodMapping;
+
+  private CfLineToMethodMapper(Map<String, Int2ObjectMap<String>> sourceMethodMapping) {
+    this.sourceMethodMapping = sourceMethodMapping;
+  }
+
+  public static CfLineToMethodMapper create(
+      AndroidApp inputApp,
+      KotlinSourceDebugExtensionCollection kotlinSourceDebugExtensions,
+      Timing timing) {
+    Set<ClassReference> kotlinClassesWithInlineFunctions =
+        getKotlinClassesWithInlineFunctions(kotlinSourceDebugExtensions, timing);
+    return new CfLineToMethodMapper(
+        readLineNumbersFromClassFiles(inputApp, kotlinClassesWithInlineFunctions, timing));
+  }
+
+  public String lookupNameAndDescriptor(String binaryName, int lineNumber)
+      throws ResourceException {
+    return sourceMethodMapping.getOrDefault(binaryName, Int2ObjectMaps.emptyMap()).get(lineNumber);
+  }
+
+  private static Set<ClassReference> getKotlinClassesWithInlineFunctions(
+      KotlinSourceDebugExtensionCollection kotlinSourceDebugExtensions, Timing timing) {
+    try (Timing t0 = timing.begin("Get kotlin classes with inline functions")) {
+      Set<ClassReference> kotlinClassesWithInlineFunctions = new HashSet<>();
+      for (KotlinSourceDebugExtensionParserResult kotlinSourceDebugExtension :
+          kotlinSourceDebugExtensions.values()) {
+        boolean first = true;
+        for (var inlineePosition : kotlinSourceDebugExtension.getInlineePositions().values()) {
+          if (first) {
+            // Skip. It is the current holder.
+            first = false;
+          } else if (inlineePosition != null) {
+            String binaryName = inlineePosition.getSource().getPath();
+            kotlinClassesWithInlineFunctions.add(Reference.classFromBinaryName(binaryName));
+          }
+        }
+      }
+      return kotlinClassesWithInlineFunctions;
+    }
+  }
+
+  private static Map<String, Int2ObjectMap<String>> readLineNumbersFromClassFiles(
+      AndroidApp inputApp, Set<ClassReference> kotlinClassesWithInlineFunctions, Timing timing) {
+    try (Timing t0 = timing.begin("Read line numbers from class files")) {
+      Map<String, Int2ObjectMap<String>> sourceMethodMapping = new HashMap<>();
+      try {
+        for (ProgramResourceProvider resourceProvider : inputApp.getProgramResourceProviders()) {
+          // TODO(b/391785584): Do not use the input providers here.
+          if (resourceProvider instanceof InternalProgramClassProvider) {
+            continue;
+          }
+          if (resourceProvider instanceof ArchiveResourceProvider) {
+            // Special case the ArchiveResourceProvider to minimize disk I/O.
+            ArchiveResourceProvider provider = (ArchiveResourceProvider) resourceProvider;
+            ArchiveResourceProviderHelper helper = new ArchiveResourceProviderHelper(provider);
+            timing.begin("Read archive");
+            helper.accept(
+                programResource ->
+                    processProgramResource(
+                        programResource,
+                        kotlinClassesWithInlineFunctions,
+                        sourceMethodMapping,
+                        timing),
+                entry -> {
+                  String name = entry.getName();
+                  if (!name.endsWith(CLASS_EXTENSION)) {
+                    return false;
+                  }
+                  String binaryName = name.substring(0, name.length() - CLASS_EXTENSION.length());
+                  return kotlinClassesWithInlineFunctions.contains(
+                      Reference.classFromBinaryName(binaryName));
+                });
+            timing.end();
+          } else {
+            ProgramResourceProviderUtils.forEachProgramResourceCompat(
+                resourceProvider,
+                programResource ->
+                    processProgramResource(
+                        programResource,
+                        kotlinClassesWithInlineFunctions,
+                        sourceMethodMapping,
+                        timing));
+          }
+        }
+      } catch (ResourceException e) {
+        throw new RuntimeException(e);
+      }
+      return sourceMethodMapping;
+    }
+  }
+
+  private static void processProgramResource(
+      ProgramResource programResource,
+      Set<ClassReference> kotlinClassesWithInlineFunctions,
+      Map<String, Int2ObjectMap<String>> sourceMethodMapping,
+      Timing timing) {
+    if (programResource.getKind() != Kind.CF) {
+      return;
+    }
+    Set<String> classDescriptors = programResource.getClassDescriptors();
+    if (classDescriptors == null || classDescriptors.size() != 1) {
+      return;
+    }
+    String classDescriptor = classDescriptors.iterator().next();
+    ClassReference classReference = Reference.classFromDescriptor(classDescriptor);
+    if (!kotlinClassesWithInlineFunctions.contains(classReference)) {
+      return;
+    }
+    timing.begin("Parse class");
+    Int2ObjectMap<String> currentLineNumberMapping = new Int2ObjectOpenHashMap<>();
+    ClassVisitor.visit(programResource, currentLineNumberMapping);
+    sourceMethodMapping.put(classReference.getBinaryName(), currentLineNumberMapping);
+    timing.end();
+  }
+
+  public static String getName(String nameAndDescriptor) {
+    int index = nameAndDescriptor.indexOf(NAME_DESCRIPTOR_SEPARATOR);
+    assert index > 0;
+    return nameAndDescriptor.substring(0, index);
+  }
+
+  public static String getDescriptor(String nameAndDescriptor) {
+    int index = nameAndDescriptor.indexOf(NAME_DESCRIPTOR_SEPARATOR);
+    assert index > 0;
+    return nameAndDescriptor.substring(index + NAME_DESCRIPTOR_SEPARATOR.length());
+  }
+
+  private static class ClassVisitor extends org.objectweb.asm.ClassVisitor {
+
+    private final Int2ObjectMap<String> lineNumberMapping;
+
+    private ClassVisitor(Int2ObjectMap<String> lineNumberMapping) {
+      super(InternalOptions.ASM_VERSION);
+      this.lineNumberMapping = lineNumberMapping;
+    }
+
+    public static void visit(
+        ProgramResource programResource, Int2ObjectMap<String> lineNumberMapping) {
+      byte[] bytes;
+      try {
+        bytes = StreamUtils.streamToByteArrayClose(programResource.getByteStream());
+      } catch (ResourceException | IOException e) {
+        // Intentionally left empty because the addition of inline info for kotlin inline
+        // functions is a best effort.
+        return;
+      }
+      new ClassReader(bytes).accept(new ClassVisitor(lineNumberMapping), ClassReader.SKIP_FRAMES);
+    }
+
+    @Override
+    public MethodVisitor visitMethod(
+        int access, String name, String descriptor, String signature, String[] exceptions) {
+      return new MethodLineVisitor(
+          name + NAME_DESCRIPTOR_SEPARATOR + descriptor, lineNumberMapping);
+    }
+  }
+
+  private static class MethodLineVisitor extends MethodVisitor {
+
+    private final String nameAndDescriptor;
+    private final Map<Integer, String> lineMethodMapping;
+
+    private MethodLineVisitor(String nameAndDescriptor, Map<Integer, String> lineMethodMapping) {
+      super(InternalOptions.ASM_VERSION);
+      this.nameAndDescriptor = nameAndDescriptor;
+      this.lineMethodMapping = lineMethodMapping;
+    }
+
+    @Override
+    public void visitLineNumber(int line, Label start) {
+      lineMethodMapping.put(line, nameAndDescriptor);
+    }
+  }
+}
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 870c77e..aace626 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1489,7 +1489,7 @@
   // If non null it must be and passed to the consumer.
   public InternalMapConsumer mapConsumer = null;
 
-  public boolean shouldOutputMappingFile() {
+  public boolean hasMappingFileSupport() {
     return mapConsumer != null;
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
index 3250b81..6da517c 100644
--- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -28,7 +28,6 @@
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ForkJoinPool;
 import java.util.function.Consumer;
-import java.util.function.Function;
 import java.util.function.Predicate;
 
 public class ThreadUtils {
@@ -150,28 +149,6 @@
         (i, item) -> "Task " + i);
   }
 
-  /** Does not process items where preprocessing returns null. */
-  public static <T, I, E extends Exception> void processItemsWithFilterPreprocessing(
-      Iterable<T> items,
-      Function<T, I> preprocessing,
-      ThrowingBiConsumer<T, I, E> consumer,
-      InternalOptions options,
-      ExecutorService executorService)
-      throws ExecutionException {
-    var tasks = new TaskCollection<>(options.getThreadingModule(), executorService);
-    for (T item : items) {
-      var info = preprocessing.apply(item);
-      if (info != null) {
-        tasks.submitUnchecked(
-            () -> {
-              consumer.accept(item, info);
-              return null;
-            });
-      }
-    }
-    tasks.await();
-  }
-
   public static <T, E extends Exception> void processItemsThatMatches(
       Collection<T> items,
       Predicate<T> itemPredicate,
diff --git a/src/main/java/com/android/tools/r8/utils/positions/AppPositionRemapper.java b/src/main/java/com/android/tools/r8/utils/positions/AppPositionRemapper.java
index cf6aed0..c46e0ac 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/AppPositionRemapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/AppPositionRemapper.java
@@ -5,6 +5,8 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.CfLineToMethodMapper;
 import com.android.tools.r8.utils.KotlinSourceDebugExtensionCollection;
 import com.android.tools.r8.utils.positions.ClassPositionRemapper.IdentityPositionRemapper;
 import com.android.tools.r8.utils.positions.ClassPositionRemapper.KotlinInlineFunctionAppPositionRemapper;
@@ -15,7 +17,7 @@
 
   ClassPositionRemapper createClassPositionRemapper(DexProgramClass clazz);
 
-  static AppPositionRemapper create(AppView<?> appView, Timing timing) {
+  static AppPositionRemapper create(AppView<?> appView, AndroidApp inputApp, Timing timing) {
     boolean identityMapping = appView.options().lineNumberOptimization.isOff();
     AppPositionRemapper positionRemapper =
         identityMapping
@@ -26,7 +28,11 @@
     // source debug extension annotation. Instantiate the kotlin remapper on top of the original
     // remapper to allow for remapping original positions to kotlin inline positions.
     var kotlinSourceDebugExtensions = KotlinSourceDebugExtensionCollection.create(appView, timing);
+    // For finding methods in Kotlin files based on SourceDebugExtensions, we use a line method map.
+    // We create it here to ensure it is only reading class files once.
+    var cfLineToMethodMapper =
+        CfLineToMethodMapper.create(inputApp, kotlinSourceDebugExtensions, timing);
     return new KotlinInlineFunctionAppPositionRemapper(
-        appView, positionRemapper, kotlinSourceDebugExtensions);
+        appView, positionRemapper, cfLineToMethodMapper, kotlinSourceDebugExtensions);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/positions/ClassPositionRemapper.java b/src/main/java/com/android/tools/r8/utils/positions/ClassPositionRemapper.java
index 5ae3009..b9ca045 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/ClassPositionRemapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/ClassPositionRemapper.java
@@ -3,19 +3,23 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.positions;
 
+import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Position.SourcePosition;
-import com.android.tools.r8.kotlin.KotlinInlineMethodMap;
+import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser;
 import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.KotlinSourceDebugExtensionParserResult;
+import com.android.tools.r8.utils.CfLineToMethodMapper;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.KotlinSourceDebugExtensionCollection;
 import com.android.tools.r8.utils.internal.collections.Pair;
+import java.util.Map;
+import java.util.Map.Entry;
 
 // PositionRemapper is a stateful function which takes a position (represented by a
 // DexDebugPositionState) and returns a remapped Position.
@@ -135,17 +139,17 @@
 
     private final AppPositionRemapper baseRemapper;
     private final DexItemFactory factory;
-    private final KotlinInlineMethodMap kotlinInlineMethodMap;
+    private final CfLineToMethodMapper lineToMethodMapper;
     private final KotlinSourceDebugExtensionCollection kotlinSourceDebugExtensions;
 
     KotlinInlineFunctionAppPositionRemapper(
         AppView<?> appView,
         AppPositionRemapper baseRemapper,
+        CfLineToMethodMapper lineToMethodMapper,
         KotlinSourceDebugExtensionCollection kotlinSourceDebugExtensions) {
       this.baseRemapper = baseRemapper;
       this.factory = appView.dexItemFactory();
-      this.kotlinInlineMethodMap = appView.getKotlinInlineMethodMap();
-      assert this.kotlinInlineMethodMap != null;
+      this.lineToMethodMapper = lineToMethodMapper;
       this.kotlinSourceDebugExtensions = kotlinSourceDebugExtensions;
     }
 
@@ -191,36 +195,58 @@
         @Override
         public Pair<Position, Position> createRemappedPosition(Position position) {
           int line = position.getLine();
-          var inlinedPosition = kotlinSourceDebugExtension.lookupInlinedPositionEntry(line);
+          Map.Entry<Integer, KotlinSourceDebugExtensionParser.Position> inlinedPosition =
+              kotlinSourceDebugExtension.lookupInlinedPosition(line);
           if (inlinedPosition == null) {
             return baseRemapper.createRemappedPosition(position);
           }
-          int inlineeLineDelta = line - inlinedPosition.start;
-          int originalInlineeLine = inlinedPosition.value.getRange().from + inlineeLineDelta;
-          String binaryName = inlinedPosition.value.getSource().getPath();
-          String clazzDescriptor = DescriptorUtils.getDescriptorFromClassInternalName(binaryName);
-          DexType inlineeType = factory.createType(clazzDescriptor);
-          DexMethod inlinee = kotlinInlineMethodMap.lookup(inlineeType, originalInlineeLine);
-          if (inlinee == null) {
-            return baseRemapper.createRemappedPosition(position);
-          }
-          if (!inlinee.equals(position.getMethod())) {
-            // We have an inline from a different method than the current position.
-            var calleePosition = kotlinSourceDebugExtension.lookupCalleePosition(line);
-            if (calleePosition != null) {
-              // Take the first line as the callee position
-              int calleeLine = Math.max(0, calleePosition.getRange().from);
-              position = position.builderWithCopy().setLine(calleeLine).build();
+          int inlineeLineDelta = line - inlinedPosition.getKey();
+          int originalInlineeLine = inlinedPosition.getValue().getRange().from + inlineeLineDelta;
+          try {
+            String binaryName = inlinedPosition.getValue().getSource().getPath();
+            String nameAndDescriptor =
+                lineToMethodMapper.lookupNameAndDescriptor(binaryName, originalInlineeLine);
+            if (nameAndDescriptor == null) {
+              return baseRemapper.createRemappedPosition(position);
             }
-            return baseRemapper.createRemappedPosition(
-                SourcePosition.builder()
-                    .setLine(originalInlineeLine)
-                    .setMethod(inlinee)
-                    .setCallerPosition(position)
-                    .build());
+            String clazzDescriptor = DescriptorUtils.getDescriptorFromClassInternalName(binaryName);
+            String methodName = CfLineToMethodMapper.getName(nameAndDescriptor);
+            String methodDescriptor = CfLineToMethodMapper.getDescriptor(nameAndDescriptor);
+            String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(methodDescriptor);
+            String[] argumentDescriptors =
+                DescriptorUtils.getArgumentTypeDescriptors(methodDescriptor);
+            DexString[] argumentDexStringDescriptors = new DexString[argumentDescriptors.length];
+            for (int i = 0; i < argumentDescriptors.length; i++) {
+              argumentDexStringDescriptors[i] = factory.createString(argumentDescriptors[i]);
+            }
+            DexMethod inlinee =
+                factory.createMethod(
+                    factory.createString(clazzDescriptor),
+                    factory.createString(methodName),
+                    factory.createString(returnTypeDescriptor),
+                    argumentDexStringDescriptors);
+            if (!inlinee.equals(position.getMethod())) {
+              // We have an inline from a different method than the current position.
+              Entry<Integer, KotlinSourceDebugExtensionParser.Position> calleePosition =
+                  kotlinSourceDebugExtension.lookupCalleePosition(line);
+              if (calleePosition != null) {
+                // Take the first line as the callee position
+                int calleeLine = Math.max(0, calleePosition.getValue().getRange().from);
+                position = position.builderWithCopy().setLine(calleeLine).build();
+              }
+              return baseRemapper.createRemappedPosition(
+                  SourcePosition.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.
+          } catch (ResourceException ignored) {
+            // Intentionally left empty. Remapping of kotlin functions utility is a best effort
+            // mapping.
           }
-          // This is the same position, so we should really not mark this as an inline position.
-          // Fall through to the default case.
           return baseRemapper.createRemappedPosition(position);
         }
 
diff --git a/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
index f0f83e5..eefbd73 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
 import com.android.tools.r8.naming.mappinginformation.ResidualSignatureMappingInformation;
 import com.android.tools.r8.shaking.KeepInfoCollection;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OriginalSourceFiles;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -44,26 +45,31 @@
 public class LineNumberOptimizer {
 
   public static ProguardMapSupplierResult runAndWriteMap(
+      AndroidApp inputApp,
       AppView<?> appView,
       ExecutorService executorService,
       Timing timing,
       OriginalSourceFiles originalSourceFiles,
       DebugRepresentationPredicate representation)
       throws ExecutionException {
-    return new LineNumberOptimizer(appView, executorService, originalSourceFiles, representation)
+    return new LineNumberOptimizer(
+            inputApp, appView, executorService, originalSourceFiles, representation)
         .runAndWriteMap(timing);
   }
 
+  private final AndroidApp inputApp;
   private final AppView<?> appView;
   private final ExecutorService executorService;
   private final OriginalSourceFiles originalSourceFiles;
   private final DebugRepresentationPredicate representation;
 
   private LineNumberOptimizer(
+      AndroidApp inputApp,
       AppView<?> appView,
       ExecutorService executorService,
       OriginalSourceFiles originalSourceFiles,
       DebugRepresentationPredicate representation) {
+    this.inputApp = inputApp;
     this.appView = appView;
     this.executorService = executorService;
     this.originalSourceFiles = originalSourceFiles;
@@ -72,8 +78,7 @@
 
   @SuppressWarnings("InconsistentOverloads")
   private ProguardMapSupplierResult runAndWriteMap(Timing timing) throws ExecutionException {
-    assert appView.options().shouldOutputMappingFile();
-    assert appView.getKotlinInlineMethodMap() != null;
+    assert appView.options().hasMappingFileSupport();
 
     if (shouldWriteOriginalMappingFile()) {
       return writeOriginalMappingFile(timing);
@@ -160,7 +165,7 @@
       PositionToMappedRangeMapper positionToMappedRangeMapper, Timing timing)
       throws ExecutionException {
     timing.begin("Process classes");
-    AppPositionRemapper positionRemapper = AppPositionRemapper.create(appView, timing);
+    AppPositionRemapper positionRemapper = AppPositionRemapper.create(appView, inputApp, timing);
     Deque<ClassPositionMapping> worklist = new ConcurrentLinkedDeque<>();
     ThreadUtils.processItemsThatMatches(
         appView.appInfo().classes(),
diff --git a/src/test/java/com/android/tools/r8/utils/ImmutableDisjointIntRangeMapTest.java b/src/test/java/com/android/tools/r8/utils/ImmutableDisjointIntRangeMapTest.java
deleted file mode 100644
index a3a6819..0000000
--- a/src/test/java/com/android/tools/r8/utils/ImmutableDisjointIntRangeMapTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (c) 2026, 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 static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.internal.collections.ImmutableDisjointIntRangeMap;
-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 ImmutableDisjointIntRangeMapTest extends TestBase {
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
-  }
-
-  public ImmutableDisjointIntRangeMapTest(TestParameters parameters) {
-    parameters.assertNoneRuntime();
-  }
-
-  @Test
-  public void testSingleRange() {
-    ImmutableDisjointIntRangeMap<String> map =
-        ImmutableDisjointIntRangeMap.<String>builder().add(10, 20, "A").build();
-    assertEquals(1, map.size());
-
-    assertNull(map.getEntry(9));
-    assertEntry(map.getEntry(10), 10, "A");
-    assertEntry(map.getEntry(15), 10, "A");
-    assertEntry(map.getEntry(20), 10, "A");
-    assertNull(map.getEntry(21));
-  }
-
-  @Test
-  public void testMultipleRangesOutOfOrder() {
-    ImmutableDisjointIntRangeMap<Character> map =
-        ImmutableDisjointIntRangeMap.<Character>builder()
-            .add(31, 100, 'a')
-            .add(1, 10, 'b')
-            .add(20, 30, 'c')
-            .build();
-    assertEquals(3, map.size());
-
-    assertEntry(map.getEntry(5), 1, 'b');
-    assertEntry(map.getEntry(25), 20, 'c');
-    assertEntry(map.getEntry(50), 31, 'a');
-  }
-
-  @Test
-  public void testAddUncovered() {
-    ImmutableDisjointIntRangeMap<String> map =
-        ImmutableDisjointIntRangeMap.<String>builder()
-            .add(10, 20, "A")
-            .addUncoveredSubRanges(10, 20, "B")
-            .addUncoveredSubRanges(12, 18, "C")
-            .addUncoveredSubRanges(5, 12, "E")
-            .addUncoveredSubRanges(18, 25, (start, end) -> "D_" + start + "_" + end)
-            .addUncoveredSubRanges(8, 22, "F")
-            .addUncoveredSubRanges(30, 40, "G")
-            .build();
-
-    assertEquals(4, map.size());
-    assertNull(map.getEntry(4));
-    assertEntry(map.getEntry(7), 5, "E");
-    assertEntry(map.getEntry(15), 10, "A");
-    assertEntry(map.getEntry(23), 21, "D_21_25");
-    assertNull(map.getEntry(26));
-    assertEntry(map.getEntry(35), 30, "G");
-    assertNull(map.getEntry(41));
-  }
-
-  private <V> void assertEntry(
-      ImmutableDisjointIntRangeMap.Entry<V> entry, int expectedStart, V expectedValue) {
-    assertNotNull(entry);
-    assertEquals(expectedStart, entry.start);
-    assertEquals(expectedValue, entry.value);
-  }
-}
diff --git a/src/test/java8/kotlin/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java b/src/test/java8/kotlin/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java
index 162f31b..4aff93a 100644
--- a/src/test/java8/kotlin/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java
+++ b/src/test/java8/kotlin/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java
@@ -55,8 +55,8 @@
         KotlinSourceDebugExtensionParser.parse(annotationData);
     assertNotNull(result);
     assertEquals(1, result.inlinePositionsCount());
-    assertEquals(1, result.lookupInlinedPositionEntry(1).start);
-    Position position = result.lookupInlinedPosition(1);
+    assertEquals(1, (int) result.lookupInlinedPosition(1).getKey());
+    Position position = result.lookupInlinedPosition(1).getValue();
     assertEquals("EnumSwitch.kt", position.getSource().getFileName());
     assertEquals("enumswitch/EnumSwitchKt", position.getSource().getPath());
     assertEquals(1, position.getRange().from);
@@ -97,20 +97,20 @@
         KotlinSourceDebugExtensionParser.parse(annotationData);
     assertNotNull(result);
     assertEquals(3, result.inlinePositionsCount());
-    assertEquals(1, result.lookupInlinedPositionEntry(1).start);
-    assertEquals(23, result.lookupInlinedPositionEntry(23).start);
-    assertEquals(24, result.lookupInlinedPositionEntry(24).start);
+    assertEquals(1, (int) result.lookupInlinedPosition(1).getKey());
+    assertEquals(23, (int) result.lookupInlinedPosition(23).getKey());
+    assertEquals(24, (int) result.lookupInlinedPosition(24).getKey());
 
     // Check that files are correctly parsed.
-    Position pos1 = result.lookupInlinedPosition(1);
+    Position pos1 = result.lookupInlinedPosition(1).getValue();
     assertEquals("Main.kt", pos1.getSource().getFileName());
     assertEquals("retrace/MainKt", pos1.getSource().getPath());
 
-    Position pos2 = result.lookupInlinedPosition(23);
+    Position pos2 = result.lookupInlinedPosition(23).getValue();
     assertEquals("InlineFunction.kt", pos2.getSource().getFileName());
     assertEquals("retrace/InlineFunctionKt", pos2.getSource().getPath());
 
-    Position pos3 = result.lookupInlinedPosition(24);
+    Position pos3 = result.lookupInlinedPosition(24).getValue();
     assertEquals("InlineFunction.kt", pos3.getSource().getFileName());
     assertEquals("retrace/InlineFunction", pos3.getSource().getPath());
 
@@ -318,8 +318,8 @@
     KotlinSourceDebugExtensionParserResult parsedResult =
         KotlinSourceDebugExtensionParser.parse(annotationData);
     assertNotNull(parsedResult);
-    assertEquals(24, parsedResult.lookupInlinedPositionEntry(25).start);
-    Position value = parsedResult.lookupInlinedPosition(25);
+    assertEquals(24, (int) parsedResult.lookupInlinedPosition(25).getKey());
+    Position value = parsedResult.lookupInlinedPosition(25).getValue();
     assertEquals(12, value.getRange().from);
     assertEquals(13, value.getRange().to);
   }
diff --git a/src/test/java8/kotlin/com/android/tools/r8/partial/kotlin/InlineTest.kt b/src/test/java8/kotlin/com/android/tools/r8/partial/kotlin/InlineTest.kt
deleted file mode 100644
index 34b6e62..0000000
--- a/src/test/java8/kotlin/com/android/tools/r8/partial/kotlin/InlineTest.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) 2026, 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.partial.kotlin
-
-class Inlinee {
-  inline fun inlineeMethod(): String {
-    return "inlined!"
-  }
-}
-
-class Caller {
-  fun call(): String {
-    return Inlinee().inlineeMethod()
-  }
-}
-
-fun main() {
-  println(Caller().call())
-}
diff --git a/src/test/java8/kotlin/com/android/tools/r8/partial/kotlin/PartialCompilationKotlinInlineFunctionTest.java b/src/test/java8/kotlin/com/android/tools/r8/partial/kotlin/PartialCompilationKotlinInlineFunctionTest.java
deleted file mode 100644
index 2bf1833..0000000
--- a/src/test/java8/kotlin/com/android/tools/r8/partial/kotlin/PartialCompilationKotlinInlineFunctionTest.java
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2026, 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.partial.kotlin;
-
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import com.android.tools.r8.KotlinCompileMemoizer;
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
-import com.android.tools.r8.KotlinTestBase;
-import com.android.tools.r8.KotlinTestParameters;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.util.List;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class PartialCompilationKotlinInlineFunctionTest extends KotlinTestBase {
-
-  private static final String PKG =
-      PartialCompilationKotlinInlineFunctionTest.class.getPackage().getName();
-  private static final String CALLER_NAME = PKG + ".Caller";
-  private static final String MAIN_NAME = PKG + ".InlineTestKt";
-
-  private final TestParameters parameters;
-
-  @Parameters(name = "{0}, kotlin: {1}")
-  public static List<Object[]> data() {
-    return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(),
-        getKotlinTestParameters().withAllCompilersAndLambdaGenerations().build());
-  }
-
-  public PartialCompilationKotlinInlineFunctionTest(
-      TestParameters parameters, KotlinTestParameters kotlinParameters) {
-    super(kotlinParameters);
-    this.parameters = parameters;
-  }
-
-  @Test
-  public void test() throws Exception {
-    parameters.assumeCanUseR8Partial();
-    KotlinCompiler kotlinc = kotlinParameters.getCompiler();
-    testForR8Partial(parameters.getBackend())
-        .addProgramFiles(compiledJars.getForConfiguration(kotlinParameters))
-        .addProgramFiles(kotlinc.getKotlinStdlibJar())
-        .addProgramFiles(kotlinc.getKotlinAnnotationJar())
-        .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
-        .addKeepMainRule(MAIN_NAME)
-        .addKeepClassAndMembersRules(CALLER_NAME)
-        .setMinApi(parameters)
-        .setR8PartialConfiguration(
-            partialConfigurationBuilder -> {
-              // Put Caller and Main in R8 partition; Inlinee is in D8 partition.
-              partialConfigurationBuilder
-                  .addJavaTypeIncludePattern(CALLER_NAME)
-                  .addJavaTypeIncludePattern(MAIN_NAME);
-            })
-        .allowDiagnosticMessages()
-        .allowUnusedDontWarnPatterns()
-        .compile()
-        .inspect(this::inspect)
-        .run(parameters.getRuntime(), MAIN_NAME)
-        .assertSuccessWithOutputLines("inlined!");
-  }
-
-  private void inspect(CodeInspector inspector) {
-    ClassSubject callerClass = inspector.clazz(CALLER_NAME);
-    assertThat(callerClass, isPresent());
-    MethodSubject callMethod = callerClass.uniqueMethodWithOriginalName("call");
-    assertThat(callMethod, isPresent());
-  }
-
-  private static final KotlinCompileMemoizer compiledJars =
-      getCompileMemoizer(
-          getKotlinSourceFileFromResources(
-              PartialCompilationKotlinInlineFunctionTest.class, "InlineTest"));
-}
diff --git a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
index 6c45ff3..78c2262 100644
--- a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
@@ -2970,7 +2970,7 @@
 
   public static void writeApplication(AppView<?> appView, Timing timing) throws ExecutionException {
     appView.options().tool = Tool.R8;
-    R8.writeApplication(appView, Executors.newSingleThreadExecutor(), timing);
+    R8.writeApplication(appView, null, Executors.newSingleThreadExecutor(), timing);
   }
 
   public static void disassemble(AndroidApp app, PrintStream ps)
diff --git a/src/utils/java/com/android/tools/r8/utils/internal/collections/ImmutableDisjointIntRangeMap.java b/src/utils/java/com/android/tools/r8/utils/internal/collections/ImmutableDisjointIntRangeMap.java
deleted file mode 100644
index 64c5d23..0000000
--- a/src/utils/java/com/android/tools/r8/utils/internal/collections/ImmutableDisjointIntRangeMap.java
+++ /dev/null
@@ -1,240 +0,0 @@
-// Copyright (c) 2026, 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.internal.collections;
-
-import java.util.Arrays;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.function.BiFunction;
-import java.util.function.Consumer;
-
-/**
- * A specialized version of {@link SegmentTree} with tiny size.
- *
- * <p>All ranges are inclusive on both bounds.
- *
- * <p>Use {@link #builder()} to construct.
- */
-public class ImmutableDisjointIntRangeMap<V> {
-
-  private static final ImmutableDisjointIntRangeMap<Object> EMPTY =
-      new ImmutableDisjointIntRangeMap<>(new int[0], new int[0], new Object[0]);
-
-  private final int[] starts;
-  private final int[] ends;
-  private final V[] values;
-
-  private ImmutableDisjointIntRangeMap(int[] starts, int[] ends, V[] values) {
-    this.starts = starts;
-    this.ends = ends;
-    this.values = values;
-  }
-
-  @SuppressWarnings("unchecked")
-  public static <V> ImmutableDisjointIntRangeMap<V> empty() {
-    return (ImmutableDisjointIntRangeMap<V>) EMPTY;
-  }
-
-  public static <V> Builder<V> builder() {
-    return new Builder<>();
-  }
-
-  public int size() {
-    return starts.length;
-  }
-
-  public boolean isEmpty() {
-    return starts.length == 0;
-  }
-
-  public static class Entry<V> {
-    public final int start;
-    public final V value;
-
-    private Entry(int start, V value) {
-      this.start = start;
-      this.value = value;
-    }
-  }
-
-  public Entry<V> getEntry(int point) {
-    int index = findRangeIndex(point);
-    return index >= 0 ? new Entry<>(starts[index], values[index]) : null;
-  }
-
-  public V get(int point) {
-    int index = findRangeIndex(point);
-    return index >= 0 ? values[index] : null;
-  }
-
-  /** Returns the entry index of the given point, or -1. */
-  private int findRangeIndex(int point) {
-    int index = Arrays.binarySearch(starts, point);
-    if (index >= 0) {
-      return index;
-    }
-    // Arrays.binarySearch returns, index = -(insertion point) - 1.
-    // So, insertion point = -(index + 1), simplified to -index - 1
-    // The greatest element less than point is one earlier, so:
-    int candidate = -index - 2;
-    if (candidate >= 0 && point <= ends[candidate]) {
-      return candidate;
-    }
-    return -1;
-  }
-
-  public void forEach(Consumer<? super V> consumer) {
-    for (V value : values) {
-      consumer.accept(value);
-    }
-  }
-
-  public static class Builder<V> {
-
-    private static class RangeEntry<V> {
-      final int end;
-      final V value;
-
-      RangeEntry(int end, V value) {
-        this.end = end;
-        this.value = value;
-      }
-    }
-
-    private final TreeMap<Integer, RangeEntry<V>> ranges = new TreeMap<>();
-
-    /**
-     * @param start start of the range (inclusive)
-     * @param end end of the range (inclusive)
-     * @param value value of the range (non-null)
-     * @throws OverlappingRangeException if this entry overlaps with existing range(s)
-     * @throws IllegalArgumentException if the range is invalid
-     */
-    public Builder<V> add(int start, int end, V value) {
-      checkValidRange(start, end);
-      checkNonOverlapping(start, end);
-      addWithoutRangeChecks(start, end, value);
-      return this;
-    }
-
-    private void addWithoutRangeChecks(int start, int end, V value) {
-      if (value == null) {
-        throw new IllegalArgumentException(
-            "Cannot insert null values [" + start + ", " + end + "]");
-      }
-      ranges.put(start, new RangeEntry<>(end, value));
-    }
-
-    /** See {@link #addUncoveredSubRanges(int, int, BiFunction)}. */
-    public Builder<V> addUncoveredSubRanges(int start, int end, V value) {
-      return addUncoveredSubRanges(start, end, (subStart, subEnd) -> value);
-    }
-
-    /**
-     * Adds the portions of the given range that are not already covered by existing ranges,
-     * splitting the range into disjoint sub-intervals if necessary. Portions of the range that
-     * overlap with already added ranges are skipped.
-     */
-    public Builder<V> addUncoveredSubRanges(
-        int start, int end, BiFunction<Integer, Integer, V> valueFactory) {
-      checkValidRange(start, end);
-      int current = start;
-      Map.Entry<Integer, RangeEntry<V>> floor = ranges.floorEntry(start);
-      if (floor != null && floor.getValue().end >= current) {
-        if (floor.getValue().end == Integer.MAX_VALUE) {
-          return this;
-        }
-        current = floor.getValue().end + 1;
-      }
-      while (current <= end) {
-        Map.Entry<Integer, RangeEntry<V>> next = ranges.ceilingEntry(current);
-        if (next == null || next.getKey() > end) {
-          V value = valueFactory.apply(current, end);
-          addWithoutRangeChecks(current, end, value);
-          break;
-        }
-        if (next.getKey() > current) {
-          int gapEnd = next.getKey() - 1;
-          V value = valueFactory.apply(current, gapEnd);
-          addWithoutRangeChecks(current, gapEnd, value);
-        }
-        if (next.getValue().end == Integer.MAX_VALUE) {
-          break;
-        }
-        var nextCurrent = next.getValue().end + 1;
-        assert nextCurrent > current : "stepped from " + current + " to " + nextCurrent;
-        current = nextCurrent;
-      }
-      return this;
-    }
-
-    public ImmutableDisjointIntRangeMap<V> build() {
-      if (ranges.isEmpty()) {
-        return empty();
-      }
-      int size = ranges.size();
-      int[] starts = new int[size];
-      int[] ends = new int[size];
-      @SuppressWarnings("unchecked")
-      V[] values = (V[]) new Object[size];
-      int i = 0;
-      for (Map.Entry<Integer, RangeEntry<V>> entry : ranges.entrySet()) {
-        starts[i] = entry.getKey();
-        ends[i] = entry.getValue().end;
-        values[i] = entry.getValue().value;
-        i++;
-      }
-      return new ImmutableDisjointIntRangeMap<>(starts, ends, values);
-    }
-
-    public static class OverlappingRangeException extends IllegalArgumentException {
-      public OverlappingRangeException(String s) {
-        super(s);
-      }
-    }
-
-    /**
-     * @throws IllegalArgumentException if invalid.
-     */
-    private static void checkValidRange(int start, int end) {
-      if (start > end) {
-        throw new IllegalArgumentException("Invalid range: " + start + " > " + end);
-      }
-    }
-
-    /**
-     * @throws OverlappingRangeException if overlapping.
-     */
-    private void checkNonOverlapping(int start, int end) {
-      Map.Entry<Integer, RangeEntry<V>> floorEntry = ranges.floorEntry(start);
-      if (floorEntry != null && floorEntry.getValue().end >= start) {
-        throw new OverlappingRangeException(
-            "Range ["
-                + start
-                + ", "
-                + end
-                + "] overlaps with existing range ["
-                + floorEntry.getKey()
-                + ", "
-                + floorEntry.getValue().end
-                + "]");
-      }
-
-      Map.Entry<Integer, RangeEntry<V>> ceilingEntry = ranges.ceilingEntry(start);
-      if (ceilingEntry != null && end >= ceilingEntry.getKey()) {
-        throw new OverlappingRangeException(
-            "Range ["
-                + start
-                + ", "
-                + end
-                + "] overlaps with existing range ["
-                + ceilingEntry.getKey()
-                + ", "
-                + ceilingEntry.getValue().end
-                + "]");
-      }
-    }
-  }
-}