Merge "Revert "Loosen desugaring constraints""
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 1cc21a3..f14d66a 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
@@ -130,6 +130,12 @@
     debugValues.remove(value);
   }
 
+  public void removeDebugValue(Value value) {
+    assert debugValues.contains(value);
+    value.removeDebugUser(this);
+    debugValues.remove(value);
+  }
+
   /**
    * Returns the basic block containing this instruction.
    */
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 2ab087c..569e554 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -1850,14 +1850,29 @@
 
   private void processLocalVariablesAtControlEdge(AbstractInsnNode insn, IRBuilder builder) {
     assert isControlFlowInstruction(insn) && !isReturn(insn);
-    if (!(insn.getNext() instanceof LabelNode)) {
-      return;
+    int offset = getOffset(insn);
+    int blockOffset = builder.getCFG().headMap(offset).lastIntKey();
+    BlockInfo blockInfo = builder.getCFG().get(blockOffset);
+    // Read all locals that are not live on all successors to ensure liveness.
+    for (Local local : state.getLocals()) {
+      if (!liveAtAllSuccessors(blockInfo, local)) {
+        builder.addDebugLocalRead(local.slot.register, local.info);
+      }
     }
-    // If the label is the end of any local-variable scopes read the locals to ensure liveness.
-    LabelNode label = (LabelNode) insn.getNext();
-    for (Local local : state.getLocalsToClose(label)) {
-      builder.addDebugLocalRead(local.slot.register, local.info);
+  }
+
+  private boolean liveAtAllSuccessors(BlockInfo blockInfo, Local local) {
+    for (int successor : blockInfo.normalSuccessors) {
+      if (!state.localLiveAt(local, successor, this)) {
+        return false;
+      }
     }
+    for (int successor : blockInfo.exceptionalSuccessors) {
+      if (!state.localLiveAt(local, successor, this)) {
+        return false;
+      }
+    }
+    return true;
   }
 
   private void processLocalVariablesAtExit(AbstractInsnNode insn, IRBuilder builder) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
index 12b62ad..c6b83a2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
@@ -219,6 +219,27 @@
     }
   }
 
+  public boolean localLiveAt(Local local, int offset, JarSourceCode source) {
+    // TODO(zerny): Precompute and sort the local ranges.
+    for (Entry<LocalVariableNode, DebugLocalInfo> entry : localVariables.entrySet()) {
+      LocalVariableNode node = entry.getKey();
+      if (entry.getValue() != local.info) {
+        continue;
+      }
+      Type type = Type.getType(node.desc);
+      int register = getLocalRegister(node.index, type);
+      if (register != local.slot.register) {
+        continue;
+      }
+      int startOffset = source.getOffset(node.start);
+      int endOffset = source.getOffset(node.end);
+      if (offset < startOffset || endOffset <= offset) {
+        return false;
+      }
+    }
+    return true;
+  }
+
   public void setBuilding() {
     assert stack.isEmpty();
     building = true;
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 8ae2147..b698fc5 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
@@ -50,7 +50,9 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.JumpInstruction;
 import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.Move;
 import com.android.tools.r8.ir.code.MoveType;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
@@ -61,6 +63,7 @@
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Switch;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.conversion.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
 import com.android.tools.r8.utils.InternalOptions;
@@ -1545,7 +1548,7 @@
         // First rewrite zero comparison.
         rewriteIfWithConstZero(block);
 
-        if (simplifyKnownBooleanCondition(dominator, block)) {
+        if (simplifyKnownBooleanCondition(code, dominator, block)) {
           continue;
         }
 
@@ -1608,8 +1611,19 @@
    *
    * which can be replaced by a fallthrough and the phi value can be replaced
    * with the boolean value itself.
+   *
+   * We also consider the forms:
+   *
+   *    ifeqz booleanValue       ifnez booleanValue
+   *      /        \              /        \
+   *      \        /              \        /
+   *      phi(1, 0)                phi(0, 1)
+   *
+   *  which can be replaced by a fallthrough and the phi value can be replaced
+   * by an xor instruction which is smaller.
    */
-  private boolean simplifyKnownBooleanCondition(DominatorTree dominator, BasicBlock block) {
+  private boolean simplifyKnownBooleanCondition(IRCode code, DominatorTree dominator,
+      BasicBlock block) {
     If theIf = block.exit().asIf();
     Value testValue = theIf.inValues().get(0);
     if (theIf.isZeroTest() && testValue.knownToBeBoolean()) {
@@ -1639,6 +1653,19 @@
                       falseNumber.isIntegerZero())) {
                 phi.replaceUsers(testValue);
                 deadPhis++;
+              } else if ((theIf.getType() == Type.NE &&
+                           trueNumber.isIntegerZero() &&
+                           falseNumber.isIntegerOne()) ||
+                         (theIf.getType() == Type.EQ &&
+                           trueNumber.isIntegerOne() &&
+                           falseNumber.isIntegerZero())) {
+                Value newOutValue = code.createValue(phi.outType(), phi.getLocalInfo());
+                phi.replaceUsers(newOutValue);
+                Instruction newInstruction = new Xor(NumericType.INT, newOutValue, testValue,
+                    trueNumber.isIntegerOne() ? trueValue : falseValue);
+                newInstruction.setBlock(phi.getBlock());
+                phi.getBlock().getInstructions().add(0, newInstruction);
+                deadPhis++;
               }
             }
           }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index a11d924..dfe10cf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
@@ -272,12 +273,17 @@
       return true;
     }
 
+    // We cannot invoke <init> on other values than |this| on Dalvik 4.4.4. Compute whether
+    // the receiver to the call was the this value at the call-site.
+    boolean receiverOfInnerCallIsThisOfOuter = invoke.asInvokeDirect().getReceiver().isThis();
+
     // Don't allow inlining a constructor into a non-constructor if the first use of the
     // un-initialized object is not an argument of an invoke of <init>.
     // Also, we cannot inline a constructor if it initializes final fields, as such is only allowed
     // from within a constructor of the corresponding class.
     // Lastly, we can only inline a constructor, if its own <init> call is on the method's class. If
-    // we inline into a constructor, calls to super.<init> are also OK.
+    // we inline into a constructor, calls to super.<init> are also OK if the receiver of the
+    // super.<init> call is the this argument.
     InstructionIterator iterator = code.instructionIterator();
     Instruction instruction = iterator.next();
     // A constructor always has the un-initialized object as the first argument.
@@ -290,11 +296,17 @@
         if (instruction.isInvokeDirect() && !seenSuperInvoke) {
           DexMethod target = instruction.asInvokeDirect().getInvokedMethod();
           seenSuperInvoke = appInfo.dexItemFactory.isConstructor(target);
+          boolean callOnSupertypeOfThisInConstructor =
+              methodHolder.isImmediateSubtypeOf(target.holder)
+                  && instruction.asInvokeDirect().getReceiver() == unInitializedObject
+                  && receiverOfInnerCallIsThisOfOuter
+                  && methodIsConstructor;
           if (seenSuperInvoke
               // Calls to init on same class are always OK.
               && target.holder != methodHolder
-              // If we are inlining into a constructor, calls to superclass init are OK.
-              && (!methodHolder.isImmediateSubtypeOf(target.holder) || !methodIsConstructor)) {
+              // If we are inlining into a constructor, calls to superclass init are OK on the
+              // |this| value in the outer context.
+              && !callOnSupertypeOfThisInConstructor) {
             return false;
           }
         }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 52653a7..bc93fcf 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -1018,9 +1018,6 @@
   }
 
   private int getSpillRegister(LiveIntervals intervals) {
-    if (intervals.isArgumentInterval()) {
-      return intervals.getSplitParent().getRegister();
-    }
     int registerNumber = nextUnusedRegisterNumber++;
     maxRegisterNumber = registerNumber;
     if (intervals.getType() == MoveType.WIDE) {
@@ -1572,17 +1569,19 @@
       LiveIntervals unhandledInterval,
       boolean needsRegisterPair,
       int candidate) {
+    List<LiveIntervals> newInactive = new ArrayList<>();
     Iterator<LiveIntervals> inactiveIterator = inactive.iterator();
     while (inactiveIterator.hasNext()) {
       LiveIntervals intervals = inactiveIterator.next();
       if ((intervals.usesRegister(candidate) ||
           (needsRegisterPair && intervals.usesRegister(candidate + 1))) &&
           intervals.overlaps(unhandledInterval)) {
-        // If these assertions trigger we have changed the way blocked parts of intervals
-        // are handled. If we ever get intervals with fixed registers in here, we need
-        // to split them before the first use in the same way that we do when spilling
-        // overlapping active intervals.
-        assert !intervals.isLinked() || intervals.isArgumentInterval();
+        if (intervals.isLinked() && !intervals.isArgumentInterval()) {
+          int nextUsePosition = intervals.firstUseAfter(unhandledInterval.getStart());
+          LiveIntervals split = intervals.splitBefore(nextUsePosition);
+          split.setRegister(intervals.getRegister());
+          newInactive.add(split);
+        }
         if (intervals.getStart() > unhandledInterval.getStart()) {
           // The inactive live intervals hasn't started yet. Clear the temporary register
           // assignment and move back to unhandled for register reassignment.
@@ -1597,6 +1596,7 @@
         }
       }
     }
+    inactive.addAll(newInactive);
   }
 
   private void spillOverlappingActiveIntervals(
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index 0d8c6b8..3c2210d 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -88,6 +88,9 @@
   }
 
   public boolean isRematerializable(LinearScanRegisterAllocator registerAllocator) {
+    if (value.isArgument()) {
+      return true;
+    }
     // TODO(ager): rematerialize const string as well.
     if (!value.isConstNumber()) {
       return false;
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
index 4a21e8a..313497c 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
@@ -26,7 +26,7 @@
     this.dst = dst;
     this.src = LinearScanRegisterAllocator.NO_REGISTER;
     this.type = type;
-    assert definition.isConstInstruction();
+    assert definition.isConstInstruction() || definition.isArgument();
     this.definition = definition;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
index 007aa6f..a669737 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
@@ -121,8 +121,14 @@
     Instruction instruction;
     Value to = new FixedRegisterValue(move.type, move.dst);
     if (move.definition != null) {
-      ConstNumber number = move.definition.asConstNumber();
-      instruction = new ConstNumber(number.type, to, number.getRawValue());
+      if (move.definition.isArgument()) {
+        int argumentRegister = move.definition.outValue().getLiveIntervals().getRegister();
+        Value from = new FixedRegisterValue(move.type, argumentRegister);
+        instruction = new Move(to, from);
+      } else {
+        ConstNumber number = move.definition.asConstNumber();
+        instruction = new ConstNumber(number.type, to, number.getRawValue());
+      }
     } else {
       Value from = new FixedRegisterValue(move.type, valueMap.get(move.src));
       instruction = new Move(to, from);
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
index 643a3c1..cd1d880 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
@@ -31,8 +31,6 @@
   // The register allocator generating moves.
   private LinearScanRegisterAllocator allocator;
   // All registers below this number are arguments.
-  // TODO(ager): Get rid of this field, we should deal with arguments and other values that can
-  // be rematerialized differently.
   private final int argumentRegisterLimit;
   // Mapping from instruction numbers to the block that start with that instruction if any.
   private final Map<Integer, BasicBlock> blockStartMap = new HashMap<>();
@@ -257,6 +255,10 @@
   // disallowed at this point we know that argument registers do not change value and
   // therefore we don't have to perform spill moves. Performing spill moves will also
   // make art reject the code because it loses type information for the argument.
+  //
+  // TODO(ager): We are dealing with some of these moves as rematerialization. However,
+  // we are still generating actual moves back to the original argument register.
+  // We should get rid of this method and avoid generating the moves in the first place.
   private void removeArgumentRestores(Set<SpillMove> moves) {
     Iterator<SpillMove> moveIterator = moves.iterator();
     while (moveIterator.hasNext()) {
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 4d707a8..925ba5b 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -135,7 +135,7 @@
    */
   protected AndroidApp compileWithR8(List<Class> classes, String proguardConfig)
       throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
-    return compileWithR8(readClasses(classes), writeTextToTempFile(proguardConfig));
+    return compileWithR8(readClasses(classes), proguardConfig);
   }
 
   /**
@@ -170,10 +170,22 @@
   /**
    * Compile an application with R8 using the supplied proguard configuration.
    */
+  protected AndroidApp compileWithR8(AndroidApp app, String proguardConfig)
+      throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
+    return compileWithR8(app, proguardConfig, null);
+  }
+
+  /**
+   * Compile an application with R8 using the supplied proguard configuration.
+   */
   protected AndroidApp compileWithR8(
       AndroidApp app, String proguardConfig, Consumer<InternalOptions> optionsConsumer)
       throws CompilationException, ProguardRuleParserException, ExecutionException, IOException {
-    return compileWithR8(app, writeTextToTempFile(proguardConfig), optionsConsumer);
+    R8Command command =
+        ToolHelper.prepareR8CommandBuilder(app)
+            .addProguardConfiguration(ImmutableList.of(proguardConfig))
+            .build();
+    return ToolHelper.runR8(command, optionsConsumer);
   }
 
   /**
@@ -194,7 +206,15 @@
    * of the specified class.
    */
   public String keepMainProguardConfiguration(Class clazz) {
-    return "-keep public class " + clazz.getCanonicalName() + " {\n"
+    return keepMainProguardConfiguration(clazz.getCanonicalName());
+  }
+
+  /**
+   * Generate a Proguard configuration for keeping the "public static void main(String[])" method
+   * of the specified class.
+   */
+  public String keepMainProguardConfiguration(String clazz) {
+    return "-keep public class " + clazz + " {\n"
         + "  public static void main(java.lang.String[]);\n"
         + "}\n";
   }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
index 4eae243..029b1ad 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
@@ -98,7 +98,7 @@
 
   public int checkLineHasAtLeastLocals(int line, String... pairs) {
     int lines = checkLines(line, entry -> checkLocalsDefined(entry, pairs));
-    assertTrue(lines > 0);
+    assertTrue("No entries found for line: " + line, lines > 0);
     return lines;
   }
 
diff --git a/src/test/java/com/android/tools/r8/debuginfo/LiveInAllBlocksTest.java b/src/test/java/com/android/tools/r8/debuginfo/LiveInAllBlocksTest.java
new file mode 100644
index 0000000..9ee78b0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/LiveInAllBlocksTest.java
@@ -0,0 +1,51 @@
+// 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 LiveInAllBlocksTest {
+
+  public static int foo(int x) {
+    if (x % 2 == 0) {
+      int y;
+      switch (x) {
+        case 2:
+          y = 1;
+          break;
+        default:
+        case 4:
+          y = 2;
+          break;
+        case 6:
+          y = 3;
+          break; // javac does not produce a line entry here.
+      }
+      if (x % 4 == 0) {
+        if (x > 0) {
+          y += 10;
+        }
+        if (x < 0) {
+          y += -10;
+        }
+        if (x == 0) {
+          x++;
+        }
+      } else {
+        if (x > 0) {
+          y += 20;
+        }
+        if (x < 0) {
+          y += -20;
+        }
+        if (x == 0) {
+          x++;
+        }
+      }
+    }
+    return x;
+  }
+
+  public static void main(String[] args) {
+    System.out.print(LiveInAllBlocksTest.foo(42));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/LiveInAllBlocksTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/LiveInAllBlocksTestRunner.java
new file mode 100644
index 0000000..01bcb27
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/LiveInAllBlocksTestRunner.java
@@ -0,0 +1,39 @@
+// 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 com.android.tools.r8.utils.AndroidApp;
+import org.junit.Test;
+
+public class LiveInAllBlocksTestRunner extends DebugInfoTestBase {
+
+  // Regression test for b/65272487.
+  @Test
+  public void testLiveInAllBlocks() throws Exception {
+    Class clazz = LiveInAllBlocksTest.class;
+    AndroidApp d8App = compileWithD8(clazz);
+    AndroidApp dxApp = getDxCompiledSources();
+
+    String expected = "42";
+    assertEquals(expected, runOnJava(clazz));
+    assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
+    assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
+
+    checkFoo(inspectMethod(d8App, clazz, "int", "foo", "int"), false);
+    checkFoo(inspectMethod(dxApp, clazz, "int", "foo", "int"), true);
+  }
+
+  private void checkFoo(DebugInfoInspector info, boolean dx) {
+    info.checkStartLine(9);
+    for (int line : new int[] {14, 18, 23, 24, 25, 27, 28, 30, 31, 34, 35, 37, 38, 40, 41}) {
+      if (dx && line == 18) {
+        // DX does not keep entry for line 18.
+        continue;
+      }
+      info.checkLineHasAtLeastLocals(line, "x", "int", "y", "int");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress63598979.java b/src/test/java/com/android/tools/r8/jasmin/Regress63598979.java
new file mode 100644
index 0000000..091a43a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress63598979.java
@@ -0,0 +1,120 @@
+// 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.jasmin;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class Regress63598979 extends JasminTestBase {
+
+  @Test
+  public void testSimplifyIf() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    clazz.addStaticMethod("test1", ImmutableList.of("Z"), "Z",
+        ".limit stack 2",
+        ".limit locals 2",
+        "  iload 0",
+        "  ifne L2",
+        "L1:",
+        "  iconst_1",
+        "  goto L3",
+        "L2:",
+        "  iconst_0",
+        "L3:",
+        "  ireturn");
+
+    clazz.addStaticMethod("test2", ImmutableList.of("Z"), "Z",
+        ".limit stack 2",
+        ".limit locals 2",
+        "  iload 0",
+        "  ifne L2",
+        "L1:",
+        "  iconst_0",
+        "  goto L3",
+        "L2:",
+        "  iconst_1",
+        "L3:",
+        "  ireturn");
+
+    clazz.addStaticMethod("test3", ImmutableList.of("Z"), "Z",
+        ".limit stack 2",
+        ".limit locals 2",
+        "  iload 0",
+        "  ifeq L2",
+        "L1:",
+        "  iconst_0",
+        "  goto L3",
+        "L2:",
+        "  iconst_1",
+        "L3:",
+        "  ireturn");
+
+
+    clazz.addStaticMethod("test4", ImmutableList.of("Z"), "Z",
+        ".limit stack 2",
+        ".limit locals 2",
+        "  iload 0",
+        "  ifeq L2",
+        "L1:",
+        "  iconst_1",
+        "  goto L3",
+        "L2:",
+        "  iconst_0",
+        "L3:",
+        "  ireturn");
+
+    clazz.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  iconst_0",
+        "  invokestatic Test/test1(Z)Z",
+        "  invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+        "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  iconst_1",
+        "  invokestatic Test/test1(Z)Z",
+        "  invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+        "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  iconst_0",
+        "  invokestatic Test/test2(Z)Z",
+        "  invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+        "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  iconst_1",
+        "  invokestatic Test/test2(Z)Z",
+        "  invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+        "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  iconst_0",
+        "  invokestatic Test/test3(Z)Z",
+        "  invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+        "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  iconst_1",
+        "  invokestatic Test/test3(Z)Z",
+        "  invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+        "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  iconst_0",
+        "  invokestatic Test/test4(Z)Z",
+        "  invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+        "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  iconst_1",
+        "  invokestatic Test/test4(Z)Z",
+        "  invokestatic java/lang/Boolean/toString(Z)Ljava/lang/String;",
+        "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "  return");
+
+    String expected = runOnJava(builder, clazz.name);
+    String artResult = runOnArtD8(builder, clazz.name);
+    assertEquals(expected, artResult);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java b/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
new file mode 100644
index 0000000..674124a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
@@ -0,0 +1,118 @@
+// 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.smali;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class RemoveWriteOfUnusedFieldsTest extends SmaliTestBase {
+
+  @Test
+  public void unreadStaticFieldsRemoved() throws Exception {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    // All these static fields are set but never read.
+    builder.addStaticField("booleanField", "Z");
+    builder.addStaticField("byteField", "B");
+    builder.addStaticField("shortField", "S");
+    builder.addStaticField("intField", "I");
+    builder.addStaticField("longField", "J");
+    builder.addStaticField("floatField", "F");
+    builder.addStaticField("doubleField", "D");
+    builder.addStaticField("charField", "C");
+    builder.addStaticField("objectField", "Ljava/lang/Object;");
+    builder.addStaticField("stringField", "Ljava/lang/String;");
+    builder.addStaticField("testField", "LTest;");
+
+    builder.addStaticMethod("void", "test", ImmutableList.of(),
+        2,
+        "const               v0, 0",
+        "sput-byte           v0, LTest;->booleanField:Z",
+        "sput-byte           v0, LTest;->byteField:B",
+        "sput-short          v0, LTest;->shortField:S",
+        "sput                v0, LTest;->intField:I",
+        "sput                v0, LTest;->floatField:F",
+        "sput-char           v0, LTest;->charField:C",
+        "sput-object         v0, LTest;->objectField:Ljava/lang/Object;",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "sput-object         v0, LTest;->testField:LTest;",
+        "const-wide          v0, 0",
+        "sput-wide           v0, LTest;->longField:J",
+        "sput-wide           v0, LTest;->doubleField:D",
+        "return-void");
+
+    builder.addMainMethod(
+        0,
+        "    invoke-static       { }, LTest;->test()V",
+        "    return-void                             ");
+
+    AndroidApp app = compileWithR8(
+        AndroidApp.fromDexProgramData(builder.compile()),
+        keepMainProguardConfiguration("Test"),
+        options -> options.inlineAccessors = false);
+
+    DexInspector inspector = new DexInspector(app);
+    MethodSubject method = inspector.clazz("Test").method("void", "test", ImmutableList.of());
+    DexCode code = method.getMethod().getCode().asDexCode();
+    assertTrue(code.isEmptyVoidMethod());
+  }
+
+  @Test
+  public void unreadInstanceFieldsRemoved() throws Exception {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    // All these instance fields are set but never read.
+    builder.addInstanceField("booleanField", "Z");
+    builder.addInstanceField("byteField", "B");
+    builder.addInstanceField("shortField", "S");
+    builder.addInstanceField("intField", "I");
+    builder.addInstanceField("longField", "J");
+    builder.addInstanceField("floatField", "F");
+    builder.addInstanceField("doubleField", "D");
+    builder.addInstanceField("charField", "C");
+    builder.addInstanceField("objectField", "Ljava/lang/Object;");
+    builder.addInstanceField("stringField", "Ljava/lang/String;");
+    builder.addInstanceField("testField", "LTest;");
+
+    builder.addInstanceMethod("void", "test", ImmutableList.of(),
+        2,
+        "const               v0, 0",
+        "iput-byte           v0, p0, LTest;->booleanField:Z",
+        "iput-byte           v0, p0, LTest;->byteField:B",
+        "iput-short          v0, p0, LTest;->shortField:S",
+        "iput                v0, p0, LTest;->intField:I",
+        "iput                v0, p0, LTest;->floatField:F",
+        "iput-char           v0, p0, LTest;->charField:C",
+        "iput-object         v0, p0, LTest;->objectField:Ljava/lang/Object;",
+        "iput-object         v0, p0, LTest;->stringField:Ljava/lang/String;",
+        "iput-object         v0, p0, LTest;->testField:LTest;",
+        "const-wide          v0, 0",
+        "iput-wide           v0, p0, LTest;->longField:J",
+        "iput-wide           v0, p0, LTest;->doubleField:D",
+        "return-void");
+
+    builder.addMainMethod(
+        1,
+        "    new-instance         v0, LTest;",
+        "    invoke-virtual       { v0 }, LTest;->test()V",
+        "    return-void                             ");
+
+    AndroidApp app = compileWithR8(
+        AndroidApp.fromDexProgramData(builder.compile()),
+        keepMainProguardConfiguration("Test"),
+        options -> options.inlineAccessors = false);
+
+    DexInspector inspector = new DexInspector(app);
+    MethodSubject method = inspector.clazz("Test").method("void", "test", ImmutableList.of());
+    DexCode code = method.getMethod().getCode().asDexCode();
+    assertTrue(code.isEmptyVoidMethod());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index e2006d0..56059f6 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -24,7 +24,6 @@
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.DexInspector;
@@ -235,6 +234,45 @@
       addStaticField(name, type, null);
     }
 
+    public void addInstanceField(String name, String type) {
+      StringBuilder builder = new StringBuilder();
+      builder.append(".field ");
+      builder.append(name);
+      builder.append(":");
+      builder.append(type);
+      getSource(currentClassName).add(builder.toString());
+    }
+
+    private MethodSignature addMethod(String flags, String returnType, String name,
+        List<String> parameters, int locals, String code) {
+      StringBuilder builder = new StringBuilder();
+      builder.append(".method ");
+      if (flags != null && flags.length() > 0) {
+        builder.append(flags);
+        builder.append(" ");
+      }
+      builder.append(name);
+      builder.append("(");
+      for (String parameter : parameters) {
+        builder.append(DescriptorUtils.javaTypeToDescriptor(parameter));
+      }
+      builder.append(")");
+      builder.append(DescriptorUtils.javaTypeToDescriptor(returnType));
+      builder.append("\n");
+      if (locals >= 0) {
+        builder.append(".locals ");
+        builder.append(locals);
+        builder.append("\n\n");
+        assert code != null;
+        builder.append(code);
+      } else {
+        assert code == null;
+      }
+      builder.append(".end method");
+      getSource(currentClassName).add(builder.toString());
+      return new MethodSignature(currentClassName, name, returnType, parameters);
+    }
+
     public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
         int locals, String... instructions) {
       StringBuilder builder = new StringBuilder();
@@ -266,43 +304,27 @@
     private MethodSignature addStaticMethod(String flags, String returnType, String name,
         List<String> parameters, int locals, String code) {
       StringBuilder builder = new StringBuilder();
-      builder.append(".method public static ");
-      if (flags != null && flags.length() > 0) {
-        builder.append(flags);
-        builder.append(" ");
-      }
-      builder.append(name);
-      builder.append("(");
-      for (String parameter : parameters) {
-        builder.append(DescriptorUtils.javaTypeToDescriptor(parameter));
-      }
-      builder.append(")");
-      builder.append(DescriptorUtils.javaTypeToDescriptor(returnType));
-      builder.append("\n");
-      builder.append(".locals ");
-      builder.append(locals);
-      builder.append("\n\n");
-      builder.append(code);
-      builder.append(".end method");
-      getSource(currentClassName).add(builder.toString());
-      return new MethodSignature(currentClassName, name, returnType, parameters);
+      return addMethod("public static " + flags, returnType, name, parameters, locals, code);
     }
 
     public MethodSignature addAbstractMethod(
         String returnType, String name, List<String> parameters) {
+      return addMethod("public abstract", returnType, name, parameters, -1, null);
+    }
+
+    public MethodSignature addInstanceMethod(String returnType, String name, List<String> parameters,
+        int locals, String... instructions) {
       StringBuilder builder = new StringBuilder();
-      builder.append(".method public abstract ");
-      builder.append(name);
-      builder.append("(");
-      for (String parameter : parameters) {
-        builder.append(DescriptorUtils.javaTypeToDescriptor(parameter));
+      for (String instruction : instructions) {
+        builder.append(instruction);
+        builder.append("\n");
       }
-      builder.append(")");
-      builder.append(DescriptorUtils.javaTypeToDescriptor(returnType));
-      builder.append("\n");
-      builder.append(".end method");
-      getSource(currentClassName).add(builder.toString());
-      return new MethodSignature(currentClassName, name, returnType, parameters);
+      return addInstanceMethod(returnType, name, parameters, locals, builder.toString());
+    }
+
+    public MethodSignature addInstanceMethod(String returnType, String name, List<String> parameters,
+        int locals, String code) {
+      return addMethod("public", returnType, name, parameters, locals, code);
     }
 
     public MethodSignature addMainMethod(int locals, String... instructions) {
diff --git a/third_party/kotlin.tar.gz.sha1 b/third_party/kotlin.tar.gz.sha1
index 942c691..f9d8e24 100644
--- a/third_party/kotlin.tar.gz.sha1
+++ b/third_party/kotlin.tar.gz.sha1
@@ -1 +1 @@
-21b6244cb0a5bca1f1d046d00540a610c02cd714
\ No newline at end of file
+28f3708659a1daa879bc8c707da54e3617a2070f
\ No newline at end of file
diff --git a/tools/notify.py b/tools/notify.py
new file mode 100644
index 0000000..f7ae50e
--- /dev/null
+++ b/tools/notify.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+# 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.
+
+try:
+  import gi
+  from gi.repository import Notify
+  Notify.init("R8 build tools")
+
+  def notify(message):
+    try:
+      Notify.Notification.new("R8 build tools", message).show()
+    except:
+      return
+
+except ImportError:
+  def notify(message):
+    return
diff --git a/tools/test.py b/tools/test.py
index ab4b0d6..c0a1e02 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -14,6 +14,7 @@
 import sys
 import utils
 import uuid
+import notify
 
 ALL_ART_VMS = ["default", "7.0.0", "6.0.1", "5.1.1"]
 BUCKET = 'r8-test-results'
@@ -149,4 +150,9 @@
       return return_code
 
 if __name__ == '__main__':
-  sys.exit(Main())
+  return_code = Main()
+  if return_code != 0:
+    notify.notify("Tests failed.")
+  else:
+    notify.notify("Tests passed.")
+  sys.exit(return_code)