Remove DebugLocalWrite when it is possible

- It allows to D8 to generate smaller code in debug mode. It save about
12k on framework and 46k on GMSCore v10.

Bug: 66161160

Change-Id: Ic9169ca1d89cc990c779f026ad9f60cb4f23e568
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 454d6cd..7248df3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1441,6 +1441,23 @@
     }
   }
 
+  // TODO(mikaelpeltier) Manage that from and to instruction do not belong to the same block.
+  private boolean hasLineChangeBetween(Instruction from, Instruction to) {
+    if (from.getBlock() != to.getBlock()) {
+      return true;
+    }
+    InstructionListIterator iterator = from.getBlock().listIterator(from);
+    while (iterator.hasNext()) {
+      Instruction instruction = iterator.next();
+      if (instruction == to) {
+        return false;
+      } else if (instruction.isDebugPosition()) {
+        return true;
+      }
+    }
+    throw new Unreachable();
+  }
+
   public void simplifyDebugLocals(IRCode code) {
     for (BasicBlock block : code.blocks) {
       for (Phi phi : block.getPhis()) {
@@ -1451,6 +1468,26 @@
           }
         }
       }
+
+      InstructionIterator iterator = code.instructionIterator();
+      while (iterator.hasNext()) {
+        Instruction instruction = iterator.next();
+        if (instruction.isDebugLocalWrite()) {
+          assert instruction.inValues().size() == 1;
+          Value inValue = instruction.inValues().get(0);
+          if (inValue.definition != null &&
+              !inValue.definition.isConstNumber() &&
+              !hasLineChangeBetween(inValue.definition, instruction) &&
+              inValue.getLocalInfo() == null &&
+              inValue.numberOfAllUsers() == 1) {
+            inValue.setLocalInfo(instruction.outValue().getLocalInfo());
+            instruction.moveDebugValues(inValue.definition);
+            instruction.outValue().replaceUsers(inValue);
+            instruction.clearDebugValues();
+            iterator.remove();
+          }
+        }
+      }
     }
   }
 
diff --git a/src/test/debugTestResources/Locals.java b/src/test/debugTestResources/Locals.java
index 1221234..a474c1e 100644
--- a/src/test/debugTestResources/Locals.java
+++ b/src/test/debugTestResources/Locals.java
@@ -338,6 +338,13 @@
     return result;
   }
 
+  public static int intAddition(int a, int b, int c) {
+    a += b;
+    b += c;
+    c = a + b;
+    return c;
+  }
+
   public static void main(String[] args) {
     noLocals();
     unusedLocals();
@@ -360,5 +367,6 @@
     regression65066975(false);
     System.out.println(localConstant(true));
     System.out.println(localConstantBis(true));
+    System.out.println(intAddition(1, 2, 6));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsTest.java b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
index 9b0a888..b6cf308 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
@@ -608,4 +608,37 @@
         checkNoLocal("result2"),
         run());
   }
+
+  /**
+   * Companion test with dex inspection
+   * {@link com.android.tools.r8.debuginfo.CodeGeneratorTestRunner#test2AddrInstruction}
+   */
+  @Test
+  public void testLocalUsedBy2AddrInstruction() throws Throwable {
+    final String className = "Locals";
+    final String methodName = "intAddition";
+    runDebugTest(className,
+        breakpoint(className, methodName),
+        run(),
+        checkLine(SOURCE_FILE, 342),
+        checkLocal("a", Value.createInt(1)),
+        checkLocal("b", Value.createInt(2)),
+        checkLocal("c", Value.createInt(6)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 343),
+        checkLocal("a", Value.createInt(3)),
+        checkLocal("b", Value.createInt(2)),
+        checkLocal("c", Value.createInt(6)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 344),
+        checkLocal("a", Value.createInt(3)),
+        checkLocal("b", Value.createInt(8)),
+        checkLocal("c", Value.createInt(6)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 345),
+        checkLocal("a", Value.createInt(3)),
+        checkLocal("b", Value.createInt(8)),
+        checkLocal("c", Value.createInt(11)),
+        run());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/CodeGeneratorTest.java b/src/test/java/com/android/tools/r8/debuginfo/CodeGeneratorTest.java
new file mode 100644
index 0000000..a586502
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/CodeGeneratorTest.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2017, 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.debuginfo;
+
+public class CodeGeneratorTest {
+
+  public static int intAddition(int a, int b, int c) {
+    a += b;
+    b += c;
+    c = a + b;
+    return c;
+  }
+
+  public static void main(String[] args) {
+    System.out.print(intAddition(1, 2, 6));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/CodeGeneratorTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/CodeGeneratorTestRunner.java
new file mode 100644
index 0000000..062a4d3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/CodeGeneratorTestRunner.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2017, 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.debuginfo;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.code.AddInt;
+import com.android.tools.r8.code.AddInt2Addr;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.Return;
+import com.android.tools.r8.utils.AndroidApp;
+import org.junit.Test;
+
+public class CodeGeneratorTestRunner extends DebugInfoTestBase {
+
+  /**
+   * Companion test checking the behavior when attached to a debugger
+   * {@link com.android.tools.r8.debug.LocalsTest#testLocalUsedBy2AddrInstruction}
+   */
+  @Test
+  public void test2AddrInstruction() throws Exception {
+    Class clazz = CodeGeneratorTest.class;
+
+    AndroidApp d8App = compileWithD8(clazz);
+    AndroidApp dxApp = getDxCompiledSources();
+
+    String expected = "11";
+    assertEquals(expected, runOnJava(clazz));
+    assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
+    assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
+
+    DebugInfoInspector inspector = inspectMethod(d8App, clazz, "int", "intAddition", "int", "int",
+        "int");
+    Instruction[] instructions = inspector.getMethod().getCode().asDexCode().instructions;
+    assertTrue(instructions[0] instanceof AddInt2Addr);
+    assertTrue(instructions[1] instanceof AddInt2Addr);
+    assertTrue(instructions[2] instanceof AddInt);
+    assertTrue(instructions[3] instanceof Return);
+  }
+
+}