Extend LineNumberOptimizer for CF and enable CF in its test.

Change-Id: Ide6fdb38ff5ee9450ada614f6bf3b25cb543122b
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index 7508ea0..644f2c2 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -35,6 +35,10 @@
     return position;
   }
 
+  public CfLabel getLabel() {
+    return label;
+  }
+
   @Override
   public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
     state.setPosition(position);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 00a94a9..f276cb2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -87,6 +87,8 @@
   private List<InvokeDirect> thisInitializers;
   private Map<NewInstance, CfLabel> newInstanceLabels;
 
+  private InternalOptions options;
+
   // Internal abstraction of the stack values and height.
   private static class Stack {
     int maxHeight = 0;
@@ -123,6 +125,7 @@
       GraphLense graphLense,
       InternalOptions options,
       AppInfoWithSubtyping appInfo) {
+    this.options = options;
     computeInitializers();
     types = new TypeVerificationHelper(code, factory, appInfo).computeVerificationTypes();
     splitExceptionalBlocks();
@@ -379,7 +382,10 @@
   private void updatePositionAndLocals(Instruction instruction) {
     Position position = instruction.getPosition();
     boolean didLocalsChange = localsChanged();
-    boolean didPositionChange = position.isSome() && position != currentPosition;
+    boolean didPositionChange =
+        position.isSome()
+            && position != currentPosition
+            && (options.debug || instruction.instructionTypeCanThrow());
     if (!didLocalsChange && !didPositionChange) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index f10ff69..7d7baf8 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCode;
@@ -119,17 +122,15 @@
   // PositionRemapper is a stateful function which takes a position (represented by a
   // DexDebugPositionState) and returns a remapped Position.
   private interface PositionRemapper {
-    Position createRemappedPosition(DexDebugPositionState positionState);
+    Position createRemappedPosition(
+        int line, DexString file, DexMethod method, Position callerPosition);
   }
 
   private static class IdentityPositionRemapper implements PositionRemapper {
     @Override
-    public Position createRemappedPosition(DexDebugPositionState positionState) {
-      return new Position(
-          positionState.getCurrentLine(),
-          positionState.getCurrentFile(),
-          positionState.getCurrentMethod(),
-          positionState.getCurrentCallerPosition());
+    public Position createRemappedPosition(
+        int line, DexString file, DexMethod method, Position callerPosition) {
+      return new Position(line, file, method, callerPosition);
     }
   }
 
@@ -137,13 +138,9 @@
     private int nextLineNumber = 1;
 
     @Override
-    public Position createRemappedPosition(DexDebugPositionState positionState) {
-      Position newPosition =
-          new Position(
-              nextLineNumber,
-              positionState.getCurrentFile(),
-              positionState.getCurrentMethod(),
-              null);
+    public Position createRemappedPosition(
+        int line, DexString file, DexMethod method, Position callerPosition) {
+      Position newPosition = new Position(nextLineNumber, file, method, null);
       ++nextLineNumber;
       return newPosition;
     }
@@ -252,57 +249,14 @@
         for (DexEncodedMethod method : methods) {
           List<MappedPosition> mappedPositions = new ArrayList<>();
 
-          if (doesContainPositions(method)) {
-            // Do the actual processing for each method.
-            DexCode dexCode = method.getCode().asDexCode();
-            DexDebugInfo debugInfo = dexCode.getDebugInfo();
-            List<DexDebugEvent> processedEvents = new ArrayList<>();
-
-            // Our pipeline will be:
-            // [debugInfo.events] -> eventFilter -> positionRemapper -> positionEventEmitter ->
-            // [processedEvents]
-            PositionEventEmitter positionEventEmitter =
-                new PositionEventEmitter(
-                    application.dexItemFactory, method.method, processedEvents);
-
-            EventFilter eventFilter =
-                new EventFilter(
-                    debugInfo.startLine,
-                    method.method,
-                    processedEvents::add,
-                    positionState -> {
-                      int currentLine = positionState.getCurrentLine();
-                      assert currentLine >= 0;
-                      Position position = positionRemapper.createRemappedPosition(positionState);
-                      mappedPositions.add(
-                          new MappedPosition(
-                              positionState.getCurrentMethod(),
-                              currentLine,
-                              positionState.getCurrentCallerPosition(),
-                              position.line));
-                      positionEventEmitter.emitPositionEvents(
-                          positionState.getCurrentPc(), position);
-                    });
-            for (DexDebugEvent event : debugInfo.events) {
-              event.accept(eventFilter);
+          Code code = method.getCode();
+          if (code != null) {
+            if (code.isDexCode() && doesContainPositions(code.asDexCode())) {
+              optimizeDexCodePositions(
+                  method, application, positionRemapper, mappedPositions, identityMapping);
+            } else if (code.isCfCode() && doesContainPositions(code.asCfCode())) {
+              optimizeCfCodePositions(method, positionRemapper, mappedPositions);
             }
-
-            DexDebugInfo optimizedDebugInfo =
-                new DexDebugInfo(
-                    positionEventEmitter.getStartLine(),
-                    debugInfo.parameters,
-                    processedEvents.toArray(new DexDebugEvent[processedEvents.size()]));
-
-            // TODO(tamaskenez) Remove this as soon as we have external tests testing not only the
-            // remapping but whether the non-positional debug events remain intact.
-            if (identityMapping) {
-              assert optimizedDebugInfo.startLine == debugInfo.startLine;
-              assert optimizedDebugInfo.events.length == debugInfo.events.length;
-              for (int i = 0; i < debugInfo.events.length; ++i) {
-                assert optimizedDebugInfo.events[i].equals(debugInfo.events[i]);
-              }
-            }
-            dexCode.setDebugInfo(optimizedDebugInfo);
           }
 
           MethodSignature originalSignature = MethodSignature.fromDexMethod(method.method);
@@ -382,21 +336,33 @@
     return classNameMapperBuilder.build();
   }
 
+  private static int getMethodStartLine(DexEncodedMethod method) {
+    Code code = method.getCode();
+    if (code == null) {
+      return 0;
+    }
+    if (code.isDexCode()) {
+      DexDebugInfo dexDebugInfo = code.asDexCode().getDebugInfo();
+      return dexDebugInfo == null ? 0 : dexDebugInfo.startLine;
+    } else if (code.isCfCode()) {
+      List<CfInstruction> instructions = code.asCfCode().getInstructions();
+      for (CfInstruction instruction : instructions) {
+        if (!(instruction instanceof CfPosition)) {
+          continue;
+        }
+        return ((CfPosition) instruction).getPosition().line;
+      }
+    }
+    return 0;
+  }
+
   // Sort by startline, then DexEncodedMethod.slowCompare.
   // Use startLine = 0 if no debuginfo.
   private static void sortMethods(List<DexEncodedMethod> methods) {
     methods.sort(
         (lhs, rhs) -> {
-          Code lhsCode = lhs.getCode();
-          Code rhsCode = rhs.getCode();
-          DexCode lhsDexCode =
-              lhsCode == null || !lhsCode.isDexCode() ? null : lhsCode.asDexCode();
-          DexCode rhsDexCode =
-              rhsCode == null || !rhsCode.isDexCode() ? null : rhsCode.asDexCode();
-          DexDebugInfo lhsDebugInfo = lhsDexCode == null ? null : lhsDexCode.getDebugInfo();
-          DexDebugInfo rhsDebugInfo = rhsDexCode == null ? null : rhsDexCode.getDebugInfo();
-          int lhsStartLine = lhsDebugInfo == null ? 0 : lhsDebugInfo.startLine;
-          int rhsStartLine = rhsDebugInfo == null ? 0 : rhsDebugInfo.startLine;
+          int lhsStartLine = getMethodStartLine(lhs);
+          int rhsStartLine = getMethodStartLine(rhs);
           int startLineDiff = lhsStartLine - rhsStartLine;
           if (startLineDiff != 0) return startLineDiff;
           return DexEncodedMethod.slowCompare(lhs, rhs);
@@ -453,10 +419,19 @@
 
   private static boolean doesContainPositions(DexEncodedMethod method) {
     Code code = method.getCode();
-    if (code == null || !code.isDexCode()) {
+    if (code == null) {
       return false;
     }
-    DexDebugInfo debugInfo = code.asDexCode().getDebugInfo();
+    if (code.isDexCode()) {
+      return doesContainPositions(code.asDexCode());
+    } else if (code.isCfCode()) {
+      return doesContainPositions(code.asCfCode());
+    }
+    return false;
+  }
+
+  private static boolean doesContainPositions(DexCode dexCode) {
+    DexDebugInfo debugInfo = dexCode.getDebugInfo();
     if (debugInfo == null) {
       return false;
     }
@@ -467,4 +442,114 @@
     }
     return false;
   }
+
+  private static boolean doesContainPositions(CfCode cfCode) {
+    List<CfInstruction> instructions = cfCode.getInstructions();
+    for (CfInstruction instruction : instructions) {
+      if (instruction instanceof CfPosition) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private static void optimizeDexCodePositions(
+      DexEncodedMethod method,
+      DexApplication application,
+      PositionRemapper positionRemapper,
+      List<MappedPosition> mappedPositions,
+      boolean identityMapping) {
+    // Do the actual processing for each method.
+    DexCode dexCode = method.getCode().asDexCode();
+    DexDebugInfo debugInfo = dexCode.getDebugInfo();
+    List<DexDebugEvent> processedEvents = new ArrayList<>();
+
+    // Our pipeline will be:
+    // [debugInfo.events] -> eventFilter -> positionRemapper -> positionEventEmitter ->
+    // [processedEvents]
+    PositionEventEmitter positionEventEmitter =
+        new PositionEventEmitter(application.dexItemFactory, method.method, processedEvents);
+
+    EventFilter eventFilter =
+        new EventFilter(
+            debugInfo.startLine,
+            method.method,
+            processedEvents::add,
+            positionState -> {
+              int currentLine = positionState.getCurrentLine();
+              assert currentLine >= 0;
+              Position position =
+                  positionRemapper.createRemappedPosition(
+                      positionState.getCurrentLine(),
+                      positionState.getCurrentFile(),
+                      positionState.getCurrentMethod(),
+                      positionState.getCurrentCallerPosition());
+              mappedPositions.add(
+                  new MappedPosition(
+                      positionState.getCurrentMethod(),
+                      currentLine,
+                      positionState.getCurrentCallerPosition(),
+                      position.line));
+              positionEventEmitter.emitPositionEvents(positionState.getCurrentPc(), position);
+            });
+    for (DexDebugEvent event : debugInfo.events) {
+      event.accept(eventFilter);
+    }
+
+    DexDebugInfo optimizedDebugInfo =
+        new DexDebugInfo(
+            positionEventEmitter.getStartLine(),
+            debugInfo.parameters,
+            processedEvents.toArray(new DexDebugEvent[processedEvents.size()]));
+
+    // TODO(b/111253214) Remove this as soon as we have external tests testing not only the
+    // remapping but whether the non-positional debug events remain intact.
+    if (identityMapping) {
+      assert optimizedDebugInfo.startLine == debugInfo.startLine;
+      assert optimizedDebugInfo.events.length == debugInfo.events.length;
+      for (int i = 0; i < debugInfo.events.length; ++i) {
+        assert optimizedDebugInfo.events[i].equals(debugInfo.events[i]);
+      }
+    }
+    dexCode.setDebugInfo(optimizedDebugInfo);
+  }
+
+  private static void optimizeCfCodePositions(
+      DexEncodedMethod method,
+      PositionRemapper positionRemapper,
+      List<MappedPosition> mappedPositions) {
+    // Do the actual processing for each method.
+    CfCode oldCode = method.getCode().asCfCode();
+    List<CfInstruction> oldInstructions = oldCode.getInstructions();
+    List<CfInstruction> newInstructions = new ArrayList<>(oldInstructions.size());
+    for (int i = 0; i < oldInstructions.size(); ++i) {
+      CfInstruction oldInstruction = oldInstructions.get(i);
+      CfInstruction newInstruction;
+      if (oldInstruction instanceof CfPosition) {
+        CfPosition cfPosition = (CfPosition) oldInstruction;
+        Position oldPosition = cfPosition.getPosition();
+        Position newPosition =
+            positionRemapper.createRemappedPosition(
+                oldPosition.line, oldPosition.file, oldPosition.method, oldPosition.callerPosition);
+        mappedPositions.add(
+            new MappedPosition(
+                oldPosition.method,
+                oldPosition.line,
+                oldPosition.callerPosition,
+                newPosition.line));
+        newInstruction = new CfPosition(cfPosition.getLabel(), newPosition);
+      } else {
+        newInstruction = oldInstruction;
+      }
+      newInstructions.add(newInstruction);
+    }
+    method.setCode(
+        new CfCode(
+            oldCode.getMethod(),
+            oldCode.getMaxStack(),
+            oldCode.getMaxLocals(),
+            newInstructions,
+            oldCode.getTryCatchRanges(),
+            oldCode.getLocalVariables()));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
index d944a75..6e1c05f 100644
--- a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
@@ -7,12 +7,18 @@
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.debug.DebugTestConfig.RuntimeKind;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
+import java.util.Collection;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 /** Tests source file and line numbers on inlined methods. */
+@RunWith(Parameterized.class)
 public class LineNumberOptimizationTest extends DebugTestBase {
 
   private static final int[] ORIGINAL_LINE_NUMBERS = {20, 7, 8, 28, 8, 20, 21, 12, 21, 22, 16, 22};
@@ -26,25 +32,53 @@
   private static final String FILE2 = CLASS2 + ".java";
   private static final String MAIN_SIGNATURE = "([Ljava/lang/String;)V";
 
+  private RuntimeKind runtimeKind;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static Collection<Object[]> setup() {
+    return ImmutableList.of(
+        new Object[] {"CF", RuntimeKind.CF}, new Object[] {"DEX", RuntimeKind.DEX});
+  }
+
+  public LineNumberOptimizationTest(String name, RuntimeKind runtimeKind) {
+    this.runtimeKind = runtimeKind;
+  }
+
   private static DebugTestConfig makeConfig(
       LineNumberOptimization lineNumberOptimization,
       boolean writeProguardMap,
-      boolean dontOptimizeByEnablingDebug)
+      boolean dontOptimizeByEnablingDebug,
+      RuntimeKind runtimeKind)
       throws Exception {
-    AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
     Path outdir = temp.newFolder().toPath();
     Path outjar = outdir.resolve("r8_compiled.jar");
-    Path proguardMapPath = writeProguardMap ? outdir.resolve("proguard.map") : null;
+
     R8Command.Builder builder =
         R8Command.builder()
             .addProgramFiles(DEBUGGEE_JAR)
-            .setMinApiLevel(minSdk.getLevel())
-            .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
-            .setMode(dontOptimizeByEnablingDebug ? CompilationMode.DEBUG : CompilationMode.RELEASE)
-            .setOutput(outjar, OutputMode.DexIndexed);
-    if (proguardMapPath != null) {
-      builder.setProguardMapOutputPath(proguardMapPath);
+            .setMode(dontOptimizeByEnablingDebug ? CompilationMode.DEBUG : CompilationMode.RELEASE);
+    DebugTestConfig config = null;
+
+    if (runtimeKind == RuntimeKind.CF) {
+      builder.setOutput(outjar, OutputMode.ClassFile);
+      config = new CfDebugTestConfig(outjar);
+    } else {
+      assert (runtimeKind == RuntimeKind.DEX);
+      AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
+      builder
+          .setMinApiLevel(minSdk.getLevel())
+          .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
+          .setOutput(outjar, OutputMode.DexIndexed);
+      config = new D8DebugTestConfig();
     }
+
+    config.addPaths(outjar);
+    if (writeProguardMap) {
+      Path proguardMapPath = outdir.resolve("proguard.map");
+      builder.setProguardMapOutputPath(proguardMapPath);
+      config.setProguardMap(proguardMapPath);
+    }
+
     ToolHelper.runR8(
         builder.build(),
         options -> {
@@ -53,46 +87,52 @@
           }
           options.enableInlining = false;
         });
-    DebugTestConfig config = new D8DebugTestConfig();
-    config.addPaths(outjar);
-    config.setProguardMap(proguardMapPath);
+
     return config;
   }
 
   @Test
   public void testIdentityCompilation() throws Throwable {
     // Compilation will fail if the identity translation does.
-    makeConfig(LineNumberOptimization.IDENTITY_MAPPING, true, false);
+    makeConfig(LineNumberOptimization.IDENTITY_MAPPING, true, false, runtimeKind);
   }
 
   @Test
   public void testNotOptimized() throws Throwable {
-    testRelease(makeConfig(LineNumberOptimization.OFF, false, false), ORIGINAL_LINE_NUMBERS);
+    testRelease(
+        makeConfig(LineNumberOptimization.OFF, false, false, runtimeKind), ORIGINAL_LINE_NUMBERS);
   }
 
   @Test
   public void testNotOptimizedWithMap() throws Throwable {
-    testRelease(makeConfig(LineNumberOptimization.OFF, true, false), ORIGINAL_LINE_NUMBERS);
+    testRelease(
+        makeConfig(LineNumberOptimization.OFF, true, false, runtimeKind), ORIGINAL_LINE_NUMBERS);
   }
 
   @Test
   public void testNotOptimizedByEnablingDebug() throws Throwable {
-    testDebug(makeConfig(LineNumberOptimization.OFF, false, true), ORIGINAL_LINE_NUMBERS_DEBUG);
+    testDebug(
+        makeConfig(LineNumberOptimization.OFF, false, true, runtimeKind),
+        ORIGINAL_LINE_NUMBERS_DEBUG);
   }
 
   @Test
   public void testNotOptimizedByEnablingDebugWithMap() throws Throwable {
-    testDebug(makeConfig(LineNumberOptimization.OFF, true, true), ORIGINAL_LINE_NUMBERS_DEBUG);
+    testDebug(
+        makeConfig(LineNumberOptimization.OFF, true, true, runtimeKind),
+        ORIGINAL_LINE_NUMBERS_DEBUG);
   }
 
   @Test
   public void testOptimized() throws Throwable {
-    testRelease(makeConfig(LineNumberOptimization.ON, false, false), OPTIMIZED_LINE_NUMBERS);
+    testRelease(
+        makeConfig(LineNumberOptimization.ON, false, false, runtimeKind), OPTIMIZED_LINE_NUMBERS);
   }
 
   @Test
   public void testOptimizedWithMap() throws Throwable {
-    testRelease(makeConfig(LineNumberOptimization.ON, true, false), ORIGINAL_LINE_NUMBERS);
+    testRelease(
+        makeConfig(LineNumberOptimization.ON, true, false, runtimeKind), ORIGINAL_LINE_NUMBERS);
   }
 
   private void testDebug(DebugTestConfig config, int[] lineNumbers) throws Throwable {