Merge "Don't compare positions for non-throwing instructions in release mode."
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 4b52a9e..0da9ead 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -95,17 +95,6 @@
     this.debugInfo = debugInfo;
   }
 
-  public boolean hasDebugPositions() {
-    if (debugInfo != null) {
-      for (DexDebugEvent event : debugInfo.events) {
-        if (event instanceof DexDebugEvent.Default) {
-          return true;
-        }
-      }
-    }
-    return false;
-  }
-
   public DexDebugInfo debugInfoWithAdditionalFirstParameter(DexString name) {
     if (debugInfo == null) {
       return null;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index f2cc53f..22a771b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -401,12 +401,6 @@
     }
   }
 
-  public boolean hasDebugPositions() {
-    checkIfObsolete();
-    assert code != null && code.isDexCode();
-    return code.asDexCode().hasDebugPositions();
-  }
-
   public int getClassFileVersion() {
     checkIfObsolete();
     assert classFileVersion >= 0;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index d3355b4..44a7a8e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -406,7 +406,13 @@
     if (other.getClass() != getClass()) {
       return false;
     }
-    if (!identicalNonValueParts(other)) {
+    // In debug mode or if the instruction can throw we must account for positions, in release mode
+    // we do want to share non-throwing instructions even if their positions differ.
+    if (instructionTypeCanThrow() || allocator.getOptions().debug) {
+      if (!identicalNonValueParts(other)) {
+        return false;
+      }
+    } else if (!identicalNonValueNonPositionParts(other)) {
       return false;
     }
     if (isInvokeDirect() && !asInvokeDirect().sameConstructorReceiverValue(other.asInvoke())) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index eb140c4..e6791f7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -253,7 +253,8 @@
             changed = true;
             int otherPredIndex = blockToIndex.get(wrapper);
             BasicBlock otherPred = block.getPredecessors().get(otherPredIndex);
-            assert Objects.equals(pred.getPosition(), otherPred.getPosition());
+            assert !allocator.getOptions().debug
+                || Objects.equals(pred.getPosition(), otherPred.getPosition());
             pred.clearCatchHandlers();
             pred.getInstructions().clear();
             equivalence.clearComputedHash(pred);
diff --git a/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTest.java b/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTest.java
new file mode 100644
index 0000000..353952b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTest.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2018, 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.release;
+
+public class ShareCommonCodeOnDistinctPositionsTest {
+
+  public static void main(String[] args) {
+    int x;
+    int len = args.length;
+    if (len > 42) {
+      x = (len - 2) + len * 2;
+    } else {
+      x = (len - 2) + len * 2;
+    }
+    System.out.println(x);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTestRunner.java b/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTestRunner.java
new file mode 100644
index 0000000..6bfd3d1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTestRunner.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2018, 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.release;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.LineNumberTable;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.Streams;
+import it.unimi.dsi.fastutil.ints.IntCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 ShareCommonCodeOnDistinctPositionsTestRunner extends TestBase {
+
+  private static final Class CLASS = ShareCommonCodeOnDistinctPositionsTest.class;
+
+  @Parameters
+  public static Backend[] parameters() {
+    return Backend.values();
+  }
+
+  private final Backend backend;
+
+  public ShareCommonCodeOnDistinctPositionsTestRunner(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void test() throws CompilationFailedException, IOException, ExecutionException {
+    AndroidAppConsumers sink = new AndroidAppConsumers();
+    ToolHelper.runR8(
+        R8Command.builder()
+            .addLibraryFiles(runtimeJar(backend))
+            .addProgramFiles(ToolHelper.getClassFileForTestClass(CLASS))
+            .setProgramConsumer(sink.wrapProgramConsumer(emptyConsumer(backend)))
+            .setDisableMinification(true)
+            .setDisableTreeShaking(true)
+            .build(),
+        options -> options.lineNumberOptimization = LineNumberOptimization.OFF);
+    CodeInspector inspector = new CodeInspector(sink.build());
+    MethodSubject method = inspector.clazz(CLASS).mainMethod();
+    // Check that the two shared lines are not in the output (they have no throwing instructions).
+    LineNumberTable lineNumberTable = method.getLineNumberTable();
+    IntCollection lines = lineNumberTable.getLines();
+    assertFalse(lines.contains(12));
+    assertFalse(lines.contains(14));
+    // Check that the two lines have been shared, e.g., there may be only one multiplication left.
+    assertEquals(
+        "Expected only one multiplcation due to instruction sharing.",
+        // TODO(b/117539423): Implement support for sharing optimizations in the CF backend.
+        backend == Backend.DEX ? 1 : 2,
+        Streams.stream(method.iterateInstructions())
+            .filter(InstructionSubject::isMultiplication)
+            .count());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
index adee8cf..15e8803 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
@@ -80,8 +80,8 @@
   }
 
   @Override
-  public boolean hasLineNumberTable() {
-    return false;
+  public LineNumberTable getLineNumberTable() {
+    return null;
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index dedb839..067280a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstString;
@@ -212,4 +213,13 @@
   public boolean isLoad() {
     return instruction instanceof CfLoad;
   }
+
+  @Override
+  public boolean isMultiplication() {
+    if (!(instruction instanceof CfArithmeticBinop)) {
+      return false;
+    }
+    int opcode = ((CfArithmeticBinop) instruction).getAsmOpcode();
+    return Opcodes.IMUL <= opcode && opcode <= Opcodes.DMUL;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index e1d74c1..ca9ce6a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -46,6 +46,16 @@
 import com.android.tools.r8.code.IputObject;
 import com.android.tools.r8.code.IputShort;
 import com.android.tools.r8.code.IputWide;
+import com.android.tools.r8.code.MulDouble;
+import com.android.tools.r8.code.MulDouble2Addr;
+import com.android.tools.r8.code.MulFloat;
+import com.android.tools.r8.code.MulFloat2Addr;
+import com.android.tools.r8.code.MulInt;
+import com.android.tools.r8.code.MulInt2Addr;
+import com.android.tools.r8.code.MulIntLit16;
+import com.android.tools.r8.code.MulIntLit8;
+import com.android.tools.r8.code.MulLong;
+import com.android.tools.r8.code.MulLong2Addr;
 import com.android.tools.r8.code.NewInstance;
 import com.android.tools.r8.code.Nop;
 import com.android.tools.r8.code.PackedSwitch;
@@ -265,4 +275,18 @@
   public boolean isSparseSwitch() {
     return instruction instanceof SparseSwitch;
   }
+
+  @Override
+  public boolean isMultiplication() {
+    return instruction instanceof MulInt
+        || instruction instanceof MulIntLit8
+        || instruction instanceof MulIntLit16
+        || instruction instanceof MulInt2Addr
+        || instruction instanceof MulFloat
+        || instruction instanceof MulFloat2Addr
+        || instruction instanceof MulLong
+        || instruction instanceof MulLong2Addr
+        || instruction instanceof MulDouble
+        || instruction instanceof MulDouble2Addr;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index f205594..f4f1660 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -6,20 +6,26 @@
 
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugEvent;
+import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugPositionState;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.JarCode;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.signature.GenericSignatureParser;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.Arrays;
 import java.util.Iterator;
-import java.util.ListIterator;
 import java.util.function.Predicate;
-import org.objectweb.asm.tree.AbstractInsnNode;
-import org.objectweb.asm.tree.LineNumberNode;
 
 public class FoundMethodSubject extends MethodSubject {
 
@@ -144,40 +150,6 @@
   }
 
   @Override
-  public boolean hasLineNumberTable() {
-    Code code = getMethod().getCode();
-    if (code.isDexCode()) {
-      DexCode dexCode = code.asDexCode();
-      if (dexCode.getDebugInfo() != null) {
-        for (DexDebugEvent event : dexCode.getDebugInfo().events) {
-          if (event instanceof DexDebugEvent.Default) {
-            return true;
-          }
-        }
-      }
-      return false;
-    }
-    if (code.isCfCode()) {
-      for (CfInstruction insn : code.asCfCode().getInstructions()) {
-        if (insn instanceof CfPosition) {
-          return true;
-        }
-      }
-      return false;
-    }
-    if (code.isJarCode()) {
-      ListIterator<AbstractInsnNode> it = code.asJarCode().getNode().instructions.iterator();
-      while (it.hasNext()) {
-        if (it.next() instanceof LineNumberNode) {
-          return true;
-        }
-      }
-      return false;
-    }
-    throw new Unreachable("Unexpected code type: " + code.getClass().getSimpleName());
-  }
-
-  @Override
   public boolean hasLocalVariableTable() {
     Code code = getMethod().getCode();
     if (code.isDexCode()) {
@@ -207,6 +179,60 @@
   }
 
   @Override
+  public LineNumberTable getLineNumberTable() {
+    Code code = getMethod().getCode();
+    if (code.isDexCode()) {
+      return getDexLineNumberTable(code.asDexCode());
+    }
+    if (code.isCfCode()) {
+      return getCfLineNumberTable(code.asCfCode());
+    }
+    if (code.isJarCode()) {
+      return getJarLineNumberTable(code.asJarCode());
+    }
+    throw new Unreachable("Unexpected code type: " + code.getClass().getSimpleName());
+  }
+
+  private LineNumberTable getJarLineNumberTable(JarCode code) {
+    throw new Unimplemented("No support for inspecting the line number table for JarCode");
+  }
+
+  private LineNumberTable getCfLineNumberTable(CfCode code) {
+    int currentLine = -1;
+    Reference2IntMap<InstructionSubject> lineNumberTable =
+        new Reference2IntOpenHashMap<>(code.getInstructions().size());
+    for (CfInstruction insn : code.getInstructions()) {
+      if (insn instanceof CfPosition) {
+        currentLine = ((CfPosition) insn).getPosition().line;
+      }
+      if (currentLine != -1) {
+        lineNumberTable.put(new CfInstructionSubject(insn), currentLine);
+      }
+    }
+    return currentLine == -1 ? null : new LineNumberTable(lineNumberTable);
+  }
+
+  private LineNumberTable getDexLineNumberTable(DexCode code) {
+    DexDebugInfo debugInfo = code.getDebugInfo();
+    if (debugInfo == null) {
+      return null;
+    }
+    Reference2IntMap<InstructionSubject> lineNumberTable =
+        new Reference2IntOpenHashMap<>(code.instructions.length);
+    DexDebugPositionState state =
+        new DexDebugPositionState(debugInfo.startLine, getMethod().method);
+    Iterator<DexDebugEvent> iterator = Arrays.asList(debugInfo.events).iterator();
+    for (Instruction insn : code.instructions) {
+      int offset = insn.getOffset();
+      while (state.getCurrentPc() < offset && iterator.hasNext()) {
+        iterator.next().accept(state);
+      }
+      lineNumberTable.put(new DexInstructionSubject(insn), state.getCurrentLine());
+    }
+    return new LineNumberTable(lineNumberTable);
+  }
+
+  @Override
   public String toString() {
     return dexMethod.toSourceString();
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index fb0e0d3..6890684 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -65,4 +65,6 @@
   boolean isPackedSwitch();
 
   boolean isSparseSwitch();
+
+  boolean isMultiplication();
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/LineNumberTable.java b/src/test/java/com/android/tools/r8/utils/codeinspector/LineNumberTable.java
new file mode 100644
index 0000000..7c9c30f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/LineNumberTable.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2018, 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.codeinspector;
+
+import it.unimi.dsi.fastutil.ints.IntCollection;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+
+public class LineNumberTable {
+  private final Reference2IntMap<InstructionSubject> lineNumberTable;
+
+  public LineNumberTable(Reference2IntMap<InstructionSubject> lineNumberTable) {
+    this.lineNumberTable = lineNumberTable;
+  }
+
+  public IntCollection getLines() {
+    return lineNumberTable.values();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 55030ca..1e21552 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -33,7 +33,11 @@
     return null;
   }
 
-  public abstract boolean hasLineNumberTable();
+  public boolean hasLineNumberTable() {
+    return getLineNumberTable() != null;
+  }
+
+  public abstract LineNumberTable getLineNumberTable();
 
   public abstract boolean hasLocalVariableTable();
 }