Revert "Refactor position remapper to better facilitate concurrency"

This reverts commit d466ac98e66916a238c57ccbb2da6e69ee834ceb.

Reason for revert: Nondeterministic mapping file

Bug: b/422947619
Change-Id: Ib6b16df9829505be5d85f0f5b0addb93414e4b95
diff --git a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
index e5db358..ed04432 100644
--- a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
+++ b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
@@ -475,13 +475,13 @@
   }
 
   public static DexInstruction getLastExecutableInstruction(DexInstruction[] instructions) {
-    for (int i = instructions.length - 1; i >= 0; i--) {
-      DexInstruction instruction = instructions[i];
+    DexInstruction lastInstruction = null;
+    for (DexInstruction instruction : instructions) {
       if (!instruction.isPayload()) {
-        return instruction;
+        lastInstruction = instruction;
       }
     }
-    return null;
+    return lastInstruction;
   }
 
   private static int estimatedDebugInfoSize(DexDebugInfo info) {
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
deleted file mode 100644
index 221ed96..0000000
--- a/src/main/java/com/android/tools/r8/utils/positions/AppPositionRemapper.java
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) 2025, 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.positions;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.utils.CfLineToMethodMapper;
-import com.android.tools.r8.utils.positions.ClassPositionRemapper.IdentityPositionRemapper;
-import com.android.tools.r8.utils.positions.ClassPositionRemapper.KotlinInlineFunctionAppPositionRemapper;
-import com.android.tools.r8.utils.positions.ClassPositionRemapper.OptimizingPositionRemapper;
-
-public interface AppPositionRemapper {
-
-  ClassPositionRemapper createClassPositionRemapper(DexProgramClass clazz);
-
-  static AppPositionRemapper create(AppView<?> appView, CfLineToMethodMapper cfLineToMethodMapper) {
-    boolean identityMapping = appView.options().lineNumberOptimization.isOff();
-    AppPositionRemapper positionRemapper =
-        identityMapping
-            ? new IdentityPositionRemapper()
-            : new OptimizingPositionRemapper(appView.options());
-
-    // Kotlin inline functions and arguments have their inlining information stored in the
-    // source debug extension annotation. Instantiate the kotlin remapper on top of the original
-    // remapper to allow for remapping original positions to kotlin inline positions.
-    return new KotlinInlineFunctionAppPositionRemapper(
-        appView, positionRemapper, cfLineToMethodMapper);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/utils/positions/ClassFilePositionToMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/ClassFilePositionToMappedRangeMapper.java
index ef6d075..99c3df9 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/ClassFilePositionToMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/ClassFilePositionToMappedRangeMapper.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.utils.Pair;
-import com.android.tools.r8.utils.timing.Timing;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -31,11 +30,10 @@
   @Override
   public List<MappedPosition> getMappedPositions(
       ProgramMethod method,
-      MethodPositionRemapper positionRemapper,
+      ClassPositionRemapper positionRemapper,
       boolean hasOverloads,
       boolean canUseDexPc,
-      int pcEncodingCutoff,
-      Timing timing) {
+      int pcEncodingCutoff) {
     return appView.options().getTestingOptions().usePcEncodingInCfForTesting
         ? getPcEncodedPositions(method, positionRemapper)
         : getMappedPositionsRemapped(method, positionRemapper, hasOverloads);
@@ -47,7 +45,7 @@
   }
 
   private List<MappedPosition> getMappedPositionsRemapped(
-      ProgramMethod method, MethodPositionRemapper positionRemapper, boolean hasOverloads) {
+      ProgramMethod method, ClassPositionRemapper positionRemapper, boolean hasOverloads) {
     List<MappedPosition> mappedPositions = new ArrayList<>();
     // Do the actual processing for each method.
     CfCode oldCode = method.getDefinition().getCode().asCfCode();
@@ -101,18 +99,21 @@
     return mappedPositions;
   }
 
+  @SuppressWarnings("UnusedVariable")
   private List<MappedPosition> getPcEncodedPositions(
-      ProgramMethod method, MethodPositionRemapper positionRemapper) {
+      ProgramMethod method, ClassPositionRemapper positionRemapper) {
     List<MappedPosition> mappedPositions = new ArrayList<>();
     // Do the actual processing for each method.
     CfCode oldCode = method.getDefinition().getCode().asCfCode();
     List<CfInstruction> oldInstructions = oldCode.getInstructions();
     List<CfInstruction> newInstructions = new ArrayList<>(oldInstructions.size() * 3);
     Position currentPosition = null;
+    boolean isFirstEntry = false;
     for (CfInstruction oldInstruction : oldInstructions) {
       if (oldInstruction.isPosition()) {
         CfPosition cfPosition = oldInstruction.asPosition();
         currentPosition = cfPosition.getPosition();
+        isFirstEntry = true;
       } else {
         if (currentPosition != null) {
           Pair<Position, Position> remappedPosition =
@@ -124,6 +125,7 @@
           newInstructions.add(position);
           newInstructions.add(position.getLabel());
         }
+        isFirstEntry = false;
         newInstructions.add(oldInstruction);
       }
     }
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 f53d3f9..5444395 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
@@ -5,13 +5,13 @@
 
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Position.SourcePosition;
 import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser;
@@ -21,7 +21,6 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Pair;
 import java.util.IdentityHashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
@@ -29,20 +28,26 @@
 // DexDebugPositionState) and returns a remapped Position.
 public interface ClassPositionRemapper {
 
-  MethodPositionRemapper createMethodPositionRemapper(List<ProgramMethod> methods);
+  Pair<Position, Position> createRemappedPosition(Position position);
 
-  class IdentityPositionRemapper
-      implements AppPositionRemapper, ClassPositionRemapper, MethodPositionRemapper {
+  static ClassPositionRemapper getPositionRemapper(
+      AppView<?> appView, CfLineToMethodMapper cfLineToMethodMapper) {
+    boolean identityMapping = appView.options().lineNumberOptimization.isOff();
+    ClassPositionRemapper positionRemapper =
+        identityMapping
+            ? new IdentityPositionRemapper()
+            : new OptimizingPositionRemapper(appView.options());
 
-    @Override
-    public ClassPositionRemapper createClassPositionRemapper(DexProgramClass clazz) {
-      return this;
-    }
+    // Kotlin inline functions and arguments have their inlining information stored in the
+    // source debug extension annotation. Instantiate the kotlin remapper on top of the original
+    // remapper to allow for remapping original positions to kotlin inline positions.
+    return new KotlinInlineFunctionPositionRemapper(
+        appView, positionRemapper, cfLineToMethodMapper);
+  }
 
-    @Override
-    public MethodPositionRemapper createMethodPositionRemapper(List<ProgramMethod> methods) {
-      return this;
-    }
+  void setCurrentMethod(DexEncodedMethod definition);
+
+  class IdentityPositionRemapper implements ClassPositionRemapper {
 
     @Override
     public Pair<Position, Position> createRemappedPosition(Position position) {
@@ -50,193 +55,157 @@
       assert position.getOutlineCallee() == null;
       return new Pair<>(position, position);
     }
+
+    @Override
+    public void setCurrentMethod(DexEncodedMethod definition) {
+      // This has no effect.
+    }
   }
 
-  class OptimizingPositionRemapper implements AppPositionRemapper, ClassPositionRemapper {
-
+  class OptimizingPositionRemapper implements ClassPositionRemapper {
     private final int maxLineDelta;
+    private DexMethod previousMethod = null;
+    private int previousSourceLine = -1;
+    private int nextOptimizedLineNumber = 1;
 
     OptimizingPositionRemapper(InternalOptions options) {
-      // TODO(b/113198295): For dex using "Constants.DBG_LINE_RANGE + Constants.DBG_LINE_BASE"
-      //  instead of 1 creates a ~30% smaller map file but the dex files gets larger due to reduced
-      //  debug info canonicalization.
+      // TODO(113198295): For dex using "Constants.DBG_LINE_RANGE + Constants.DBG_LINE_BASE"
+      // instead of 1 creates a ~30% smaller map file but the dex files gets larger due to reduced
+      // debug info canonicalization.
       maxLineDelta = options.isGeneratingClassFiles() ? Integer.MAX_VALUE : 1;
     }
 
     @Override
-    public ClassPositionRemapper createClassPositionRemapper(DexProgramClass clazz) {
-      return this;
+    public Pair<Position, Position> createRemappedPosition(Position position) {
+      assert position.getMethod() != null;
+      if (position.getMethod().isIdenticalTo(previousMethod)) {
+        assert previousSourceLine >= 0;
+        if (position.getLine() > previousSourceLine
+            && position.getLine() - previousSourceLine <= maxLineDelta) {
+          nextOptimizedLineNumber += (position.getLine() - previousSourceLine) - 1;
+        }
+      }
+
+      Position newPosition =
+          position
+              .builderWithCopy()
+              .setLine(nextOptimizedLineNumber++)
+              .setCallerPosition(null)
+              .build();
+      previousSourceLine = position.getLine();
+      previousMethod = position.getMethod();
+      return new Pair<>(position, newPosition);
     }
 
     @Override
-    public MethodPositionRemapper createMethodPositionRemapper(List<ProgramMethod> methods) {
-      return new OptimizingMethodPositionRemapper();
-    }
-
-    class OptimizingMethodPositionRemapper implements MethodPositionRemapper {
-
-      private DexMethod previousMethod = null;
-      private int previousSourceLine = -1;
-      private int nextOptimizedLineNumber = 1;
-
-      @Override
-      public Pair<Position, Position> createRemappedPosition(Position position) {
-        assert position.getMethod() != null;
-        if (position.getMethod().isIdenticalTo(previousMethod)) {
-          assert previousSourceLine >= 0;
-          if (position.getLine() > previousSourceLine
-              && position.getLine() - previousSourceLine <= maxLineDelta) {
-            nextOptimizedLineNumber += (position.getLine() - previousSourceLine) - 1;
-          }
-        }
-
-        Position newPosition =
-            position
-                .builderWithCopy()
-                .setLine(nextOptimizedLineNumber++)
-                .setCallerPosition(null)
-                .build();
-        previousSourceLine = position.getLine();
-        previousMethod = position.getMethod();
-        return new Pair<>(position, newPosition);
-      }
+    public void setCurrentMethod(DexEncodedMethod definition) {
+      // This has no effect.
     }
   }
 
-  class KotlinInlineFunctionAppPositionRemapper implements AppPositionRemapper {
+  class KotlinInlineFunctionPositionRemapper implements ClassPositionRemapper {
 
     private final AppView<?> appView;
-    private final AppPositionRemapper baseRemapper;
     private final DexItemFactory factory;
-    private final CfLineToMethodMapper lineToMethodMapper;
-
     private final Map<DexType, Result> parsedKotlinSourceDebugExtensions = new IdentityHashMap<>();
+    private final CfLineToMethodMapper lineToMethodMapper;
+    private final ClassPositionRemapper baseRemapper;
 
-    KotlinInlineFunctionAppPositionRemapper(
+    // Fields for the current context.
+    private DexEncodedMethod currentMethod;
+    private Result parsedData = null;
+
+    private KotlinInlineFunctionPositionRemapper(
         AppView<?> appView,
-        AppPositionRemapper baseRemapper,
+        ClassPositionRemapper baseRemapper,
         CfLineToMethodMapper lineToMethodMapper) {
       this.appView = appView;
-      this.baseRemapper = baseRemapper;
       this.factory = appView.dexItemFactory();
+      this.baseRemapper = baseRemapper;
       this.lineToMethodMapper = lineToMethodMapper;
     }
 
     @Override
-    public ClassPositionRemapper createClassPositionRemapper(DexProgramClass clazz) {
-      ClassPositionRemapper baseClassRemapper = baseRemapper.createClassPositionRemapper(clazz);
-      assert baseClassRemapper == baseRemapper;
-      return new KotlinInlineFunctionClassPositionRemapper(baseClassRemapper);
-    }
-
-    class KotlinInlineFunctionClassPositionRemapper implements ClassPositionRemapper {
-
-      private final ClassPositionRemapper baseRemapper;
-
-      private KotlinInlineFunctionClassPositionRemapper(ClassPositionRemapper baseRemapper) {
-        this.baseRemapper = baseRemapper;
+    public Pair<Position, Position> createRemappedPosition(Position position) {
+      assert currentMethod != null;
+      int line = position.getLine();
+      Result parsedData = getAndParseSourceDebugExtension(position.getMethod().holder);
+      if (parsedData == null) {
+        return baseRemapper.createRemappedPosition(position);
       }
-
-      @Override
-      public MethodPositionRemapper createMethodPositionRemapper(List<ProgramMethod> methods) {
-        MethodPositionRemapper baseMethodRemapper =
-            baseRemapper.createMethodPositionRemapper(methods);
-        return new KotlinInlineFunctionMethodPositionRemapper(baseMethodRemapper, methods);
+      Map.Entry<Integer, KotlinSourceDebugExtensionParser.Position> inlinedPosition =
+          parsedData.lookupInlinedPosition(line);
+      if (inlinedPosition == null) {
+        return baseRemapper.createRemappedPosition(position);
       }
-
-      class KotlinInlineFunctionMethodPositionRemapper implements MethodPositionRemapper {
-
-        private final MethodPositionRemapper baseRemapper;
-        private final DexProgramClass currentHolder;
-
-        private Result parsedData;
-
-        private KotlinInlineFunctionMethodPositionRemapper(
-            MethodPositionRemapper baseRemapper, List<ProgramMethod> methods) {
-          assert !methods.isEmpty();
-          this.baseRemapper = baseRemapper;
-          this.currentHolder = methods.iterator().next().getHolder();
-        }
-
-        @Override
-        public Pair<Position, Position> createRemappedPosition(Position position) {
-          int line = position.getLine();
-          Result parsedData = getAndParseSourceDebugExtension(position.getMethod().holder);
-          if (parsedData == null) {
-            return baseRemapper.createRemappedPosition(position);
-          }
-          Map.Entry<Integer, KotlinSourceDebugExtensionParser.Position> inlinedPosition =
-              parsedData.lookupInlinedPosition(line);
-          if (inlinedPosition == null) {
-            return baseRemapper.createRemappedPosition(position);
-          }
-          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);
-            }
-            String clazzDescriptor = DescriptorUtils.getDescriptorFromClassBinaryName(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 =
-                  parsedData.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.
-          }
+      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);
         }
-
-        private Result getAndParseSourceDebugExtension(DexType holder) {
-          if (parsedData == null) {
-            parsedData = parsedKotlinSourceDebugExtensions.get(holder);
-          }
-          if (parsedData != null || parsedKotlinSourceDebugExtensions.containsKey(holder)) {
-            return parsedData;
-          }
-          DexValueString sourceDebugExtension =
-              appView.getSourceDebugExtensionForType(currentHolder);
-          if (sourceDebugExtension != null) {
-            parsedData =
-                KotlinSourceDebugExtensionParser.parse(sourceDebugExtension.getValue().toString());
-          }
-          parsedKotlinSourceDebugExtensions.put(holder, parsedData);
-          return parsedData;
+        String clazzDescriptor = DescriptorUtils.getDescriptorFromClassBinaryName(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 =
+              parsedData.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.
       }
+      return baseRemapper.createRemappedPosition(position);
+    }
+
+    private Result getAndParseSourceDebugExtension(DexType holder) {
+      if (parsedData == null) {
+        parsedData = parsedKotlinSourceDebugExtensions.get(holder);
+      }
+      if (parsedData != null || parsedKotlinSourceDebugExtensions.containsKey(holder)) {
+        return parsedData;
+      }
+      DexClass clazz = appView.definitionFor(currentMethod.getHolderType());
+      DexValueString dexValueString = appView.getSourceDebugExtensionForType(clazz);
+      if (dexValueString != null) {
+        parsedData = KotlinSourceDebugExtensionParser.parse(dexValueString.value.toString());
+      }
+      parsedKotlinSourceDebugExtensions.put(holder, parsedData);
+      return parsedData;
+    }
+
+    @Override
+    public void setCurrentMethod(DexEncodedMethod method) {
+      this.currentMethod = method;
+      this.parsedData = null;
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
index 9430e76..0b28e8e 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
@@ -26,7 +26,6 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Position.SourcePosition;
-import com.android.tools.r8.utils.timing.Timing;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -85,7 +84,7 @@
 
     private final PositionEventEmitter positionEventEmitter;
     private final List<MappedPosition> mappedPositions;
-    private final MethodPositionRemapper positionRemapper;
+    private final ClassPositionRemapper positionRemapper;
     private final List<DexDebugEvent> processedEvents;
 
     // Keep track of what PC has been emitted.
@@ -96,7 +95,7 @@
     public DexDebugPositionStateVisitor(
         PositionEventEmitter positionEventEmitter,
         List<MappedPosition> mappedPositions,
-        MethodPositionRemapper positionRemapper,
+        ClassPositionRemapper positionRemapper,
         List<DexDebugEvent> processedEvents,
         DexItemFactory factory,
         int startLine,
@@ -182,8 +181,7 @@
   }
 
   public List<MappedPosition> optimizeDexCodePositions(
-      ProgramMethod method, MethodPositionRemapper positionRemapper, Timing timing) {
-    timing.begin("No pc mapper");
+      ProgramMethod method, ClassPositionRemapper positionRemapper) {
     List<MappedPosition> mappedPositions = new ArrayList<>();
     // Do the actual processing for each method.
     DexApplication application = appView.appInfo().app();
@@ -223,7 +221,6 @@
         || verifyIdentityMapping(method, debugInfo, optimizedDebugInfo);
 
     dexCode.setDebugInfo(optimizedDebugInfo);
-    timing.end();
     return mappedPositions;
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
index 760f493..7b67169 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.positions.PositionToMappedRangeMapper.PcBasedDebugInfoRecorder;
-import com.android.tools.r8.utils.timing.Timing;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -35,18 +34,12 @@
   }
 
   public List<MappedPosition> optimizeDexCodePositionsForPc(
-      ProgramMethod method,
-      MethodPositionRemapper positionRemapper,
-      int pcEncodingCutoff,
-      Timing timing) {
-    timing.begin("Pc mapper");
+      ProgramMethod method, ClassPositionRemapper positionRemapper, int pcEncodingCutoff) {
     List<MappedPosition> mappedPositions = new ArrayList<>();
     // Do the actual processing for each method.
     DexCode dexCode = method.getDefinition().getCode().asDexCode();
-    timing.begin("Convert to event based debug info");
     EventBasedDebugInfo debugInfo =
         getEventBasedDebugInfo(method.getDefinition(), dexCode, appView);
-    timing.end();
     IntBox firstDefaultEventPc = new IntBox(-1);
     Pair<Integer, Position> lastPosition = new Pair<>();
     DexDebugEventVisitor visitor =
@@ -69,21 +62,17 @@
                   getCurrentPc(),
                   lastPosition.getSecond(),
                   positionRemapper,
-                  mappedPositions,
-                  timing);
+                  mappedPositions);
             }
             lastPosition.setFirst(getCurrentPc());
             lastPosition.setSecond(currentPosition);
           }
         };
 
-    timing.begin("Visit events");
     for (DexDebugEvent event : debugInfo.events) {
       event.accept(visitor);
     }
-    timing.end();
 
-    timing.begin("Flush");
     int lastInstructionPc = DebugRepresentation.getLastExecutableInstruction(dexCode).getOffset();
     if (lastPosition.getSecond() != null) {
       remapAndAddForPc(
@@ -92,18 +81,13 @@
           lastInstructionPc + 1,
           lastPosition.getSecond(),
           positionRemapper,
-          mappedPositions,
-          timing);
+          mappedPositions);
     }
-    timing.end();
 
     assert !mappedPositions.isEmpty()
         || dexCode.instructions.length == 1
         || !dexCode.hasThrowingInstructions();
-    timing.begin("Record pc mapping");
     pcBasedDebugInfo.recordPcMappingFor(method, pcEncodingCutoff);
-    timing.end();
-    timing.end();
     return mappedPositions;
   }
 
@@ -112,19 +96,14 @@
       int startPc,
       int endPc,
       Position position,
-      MethodPositionRemapper remapper,
-      List<MappedPosition> mappedPositions,
-      Timing timing) {
-    timing.begin("Remap position");
+      ClassPositionRemapper remapper,
+      List<MappedPosition> mappedPositions) {
     Pair<Position, Position> remappedPosition = remapper.createRemappedPosition(position);
-    timing.end();
-    timing.begin("Update mapped positions");
     Position oldPosition = remappedPosition.getFirst();
     for (int currentPc = startPc; currentPc < endPc; currentPc++) {
       mappedPositions.add(
           new MappedPosition(oldPosition, debugInfoProvider.getPcEncoding(currentPc)));
     }
-    timing.end();
   }
 
   // This conversion *always* creates an event based debug info encoding as any non-info will
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 02a46e9..aa55443 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
@@ -36,7 +36,6 @@
 import com.android.tools.r8.utils.timing.Timing;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.List;
 
@@ -121,8 +120,6 @@
 
     // Collect which files contain which classes that need to have their line numbers optimized.
     timing.begin("Process classes");
-    AppPositionRemapper positionRemapper =
-        AppPositionRemapper.create(appView, cfLineToMethodMapper);
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       if (shouldRun(clazz, appView)) {
         runForClass(
@@ -130,7 +127,7 @@
             appView,
             representation,
             builder,
-            positionRemapper,
+            cfLineToMethodMapper,
             positionToMappedRangeMapper,
             timing);
       }
@@ -159,7 +156,7 @@
       AppView<?> appView,
       DebugRepresentationPredicate representation,
       MappedPositionToClassNameMapperBuilder builder,
-      AppPositionRemapper positionRemapper,
+      CfLineToMethodMapper cfLineToMethodMapper,
       PositionToMappedRangeMapper positionToMappedRangeMapper,
       Timing timing) {
     timing.begin("Prelude");
@@ -173,8 +170,6 @@
     renamedMethodNames.sort(DexString::compareTo);
     timing.end();
 
-    ClassPositionRemapper classPositionRemapper =
-        positionRemapper.createClassPositionRemapper(clazz);
     for (DexString methodName : renamedMethodNames) {
       List<ProgramMethod> methods = methodsByRenamedName.get(methodName);
       if (methods.size() > 1) {
@@ -190,66 +185,62 @@
         assert verifyMethodsAreKeptDirectlyOrIndirectly(appView, methods);
       }
 
+      ClassPositionRemapper positionRemapper =
+          ClassPositionRemapper.getPositionRemapper(appView, cfLineToMethodMapper);
+
       timing.begin("Process methods");
-      // We must reuse the same MethodPositionRemapper for methods with the same name.
-      MethodPositionRemapper methodPositionRemapper =
-          classPositionRemapper.createMethodPositionRemapper(methods);
       for (ProgramMethod method : methods) {
-        if (shouldRunForMethod(method, appView, methodName, methods)) {
-          List<MappedPosition> mappedPositions =
-              runForMethod(
-                  method,
-                  appView,
-                  methods,
-                  methodPositionRemapper,
-                  positionToMappedRangeMapper,
-                  representation,
-                  timing);
-          timing.begin("Add mapped positions");
-          boolean canUseDexPc =
-              methods.size() == 1 && representation.getDexPcEncodingCutoff(method) > 0;
-          classNamingBuilder.addMappedPositions(
-              method, mappedPositions, methodPositionRemapper, canUseDexPc);
-          timing.end();
-        }
+        runForMethod(
+            method,
+            appView,
+            classNamingBuilder,
+            methodName,
+            methods,
+            positionRemapper,
+            positionToMappedRangeMapper,
+            representation,
+            timing);
       }
       timing.end();
     } // for each method group, grouped by name
   }
 
-  private static boolean shouldRunForMethod(
-      ProgramMethod method, AppView<?> appView, DexString methodName, List<ProgramMethod> methods) {
-    DexEncodedMethod definition = method.getDefinition();
-    return !method.getName().isIdenticalTo(methodName)
-        || mustHaveResidualDebugInfo(appView.options(), definition)
-        || definition.isD8R8Synthesized()
-        || methods.size() > 1;
-  }
-
-  private static List<MappedPosition> runForMethod(
+  private static void runForMethod(
       ProgramMethod method,
       AppView<?> appView,
+      MappedPositionToClassNamingBuilder classNamingBuilder,
+      DexString methodName,
       List<ProgramMethod> methods,
-      MethodPositionRemapper positionRemapper,
+      ClassPositionRemapper positionRemapper,
       PositionToMappedRangeMapper positionToMappedRangeMapper,
       DebugRepresentationPredicate representation,
       Timing timing) {
+    DexEncodedMethod definition = method.getDefinition();
+    if (method.getName().isIdenticalTo(methodName)
+        && !mustHaveResidualDebugInfo(appView.options(), definition)
+        && !definition.isD8R8Synthesized()
+        && methods.size() <= 1) {
+      return;
+    }
+    positionRemapper.setCurrentMethod(definition);
     List<MappedPosition> mappedPositions;
     int pcEncodingCutoff = methods.size() == 1 ? representation.getDexPcEncodingCutoff(method) : -1;
     boolean canUseDexPc = pcEncodingCutoff > 0;
-    Code code = method.getDefinition().getCode();
-    if (code != null
-        && (code.isCfCode() || code.isDexCode())
+    if (definition.getCode() != null
+        && (definition.getCode().isCfCode() || definition.getCode().isDexCode())
         && !appView.isCfByteCodePassThrough(method)) {
       timing.begin("Get mapped positions");
       mappedPositions =
           positionToMappedRangeMapper.getMappedPositions(
-              method, positionRemapper, methods.size() > 1, canUseDexPc, pcEncodingCutoff, timing);
+              method, positionRemapper, methods.size() > 1, canUseDexPc, pcEncodingCutoff);
       timing.end();
     } else {
-      mappedPositions = Collections.emptyList();
+      mappedPositions = new ArrayList<>();
     }
-    return mappedPositions;
+
+    timing.begin("Add mapped positions");
+    classNamingBuilder.addMappedPositions(method, mappedPositions, positionRemapper, canUseDexPc);
+    timing.end();
   }
 
   @SuppressWarnings("ComplexBooleanConstant")
diff --git a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
index 3530795..b854592 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
@@ -220,7 +220,7 @@
     public MappedPositionToClassNamingBuilder addMappedPositions(
         ProgramMethod method,
         List<MappedPosition> mappedPositions,
-        MethodPositionRemapper positionRemapper,
+        ClassPositionRemapper positionRemapper,
         boolean canUseDexPc) {
       DexEncodedMethod definition = method.getDefinition();
       DexMethod residualMethod =
diff --git a/src/main/java/com/android/tools/r8/utils/positions/MethodPositionRemapper.java b/src/main/java/com/android/tools/r8/utils/positions/MethodPositionRemapper.java
deleted file mode 100644
index 98e1b50..0000000
--- a/src/main/java/com/android/tools/r8/utils/positions/MethodPositionRemapper.java
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) 2025, 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.positions;
-
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.utils.Pair;
-
-public interface MethodPositionRemapper {
-
-  Pair<Position, Position> createRemappedPosition(Position position);
-}
diff --git a/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java
index ba0e2ea..10a79ac 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.utils.timing.Timing;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -20,11 +19,10 @@
 
   List<MappedPosition> getMappedPositions(
       ProgramMethod method,
-      MethodPositionRemapper positionRemapper,
+      ClassPositionRemapper positionRemapper,
       boolean hasOverloads,
       boolean canUseDexPc,
-      int pcEncodingCutoff,
-      Timing timing);
+      int pcEncodingCutoff);
 
   void updateDebugInfoInCodeObjects();
 
@@ -53,15 +51,13 @@
     @Override
     public List<MappedPosition> getMappedPositions(
         ProgramMethod method,
-        MethodPositionRemapper positionRemapper,
+        ClassPositionRemapper positionRemapper,
         boolean hasOverloads,
         boolean canUseDexPc,
-        int pcEncodingCutoff,
-        Timing timing) {
+        int pcEncodingCutoff) {
       return canUseDexPc
-          ? pcMapper.optimizeDexCodePositionsForPc(
-              method, positionRemapper, pcEncodingCutoff, timing)
-          : noPcMapper.optimizeDexCodePositions(method, positionRemapper, timing);
+          ? pcMapper.optimizeDexCodePositionsForPc(method, positionRemapper, pcEncodingCutoff)
+          : noPcMapper.optimizeDexCodePositions(method, positionRemapper);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/utils/positions/PositionUtils.java b/src/main/java/com/android/tools/r8/utils/positions/PositionUtils.java
index 3ee5a32..9852eeb 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/PositionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/PositionUtils.java
@@ -20,7 +20,7 @@
 public class PositionUtils {
 
   public static Position remapAndAdd(
-      Position position, MethodPositionRemapper remapper, List<MappedPosition> mappedPositions) {
+      Position position, ClassPositionRemapper remapper, List<MappedPosition> mappedPositions) {
     Pair<Position, Position> remappedPosition = remapper.createRemappedPosition(position);
     Position oldPosition = remappedPosition.getFirst();
     Position newPosition = remappedPosition.getSecond();
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
index 5a1910e..dc7a609 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
@@ -329,7 +329,7 @@
       ThrowableConsumer<? super R8FullTestBuilder> configuration) {
     return environment ->
         BenchmarkBase.runner(environment)
-            .setWarmupIterations(0)
+            .setWarmupIterations(builder.warmupIterations)
             .run(
                 results -> {
                   CompilerDump dump = builder.getExtractedDump(environment);