Merge "Update version number to 0.1.0."
diff --git a/build.gradle b/build.gradle
index e2c21b6..722106c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,11 +6,15 @@
 
 apply plugin: 'java'
 apply plugin: 'idea'
-apply plugin: 'jacoco'
 apply plugin: 'com.google.protobuf'
 
 apply from: 'copyAdditionalJctfCommonFiles.gradle'
 
+
+if (project.hasProperty('with_code_coverage')) {
+    apply plugin: 'jacoco'
+}
+
 repositories {
     maven { url 'https://maven.google.com' }
     mavenCentral()
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index c94fc3c..1ba7755 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -15,7 +15,7 @@
   public final DexField field;
   public final DexAccessFlags accessFlags;
   public DexAnnotationSet annotations;
-  public final DexValue staticValue;
+  public DexValue staticValue;
 
   public DexEncodedField(DexField field, DexAccessFlags accessFlags, DexAnnotationSet annotations,
       DexValue staticValue) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index d53c577..1880668 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -48,6 +48,10 @@
     return outValue;
   }
 
+  public boolean getBooleanValue() {
+    return !isZero();
+  }
+
   public int getIntValue() {
     assert type == ConstType.INT || type == ConstType.INT_OR_FLOAT;
     return (int) value;
diff --git a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
index 9f28eea..c8a17dc2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
@@ -110,6 +110,39 @@
     };
   }
 
+  /**
+   * Returns an iterator over all dominator blocks of <code>dominated</code>.
+   *
+   * Iteration order is always the immediate dominator of the previously returned block. The
+   * iteration starts by returning <code>dominated</code>.
+   */
+  public Iterable<BasicBlock> dominatorBlocks(BasicBlock dominated) {
+    return () -> new Iterator<BasicBlock>() {
+      private BasicBlock current = dominated;
+
+      @Override
+      public boolean hasNext() {
+        return current != null;
+      }
+
+      @Override
+      public BasicBlock next() {
+        if (!hasNext()) {
+          return null;
+        } else {
+          BasicBlock result = current;
+          if (current.getNumber() == 0) {
+            current = null;
+          } else {
+            current = immediateDominator(current);
+            assert current != result;
+          }
+          return result;
+        }
+      }
+    };
+  }
+
   public BasicBlock[] getSortedBlocks() {
     return sorted;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 48c8eb0..dad58d8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -328,16 +327,57 @@
       this.graph = graph;
     }
 
-    private void processInvoke(DexEncodedMethod source, Invoke.Type type, DexMethod method) {
+    private void addClassInitializerTarget(DexClass clazz) {
+      assert clazz != null;
+      if (clazz.hasClassInitializer() && !clazz.isLibraryClass()) {
+        DexEncodedMethod possibleTarget = clazz.getClassInitializer();
+        addTarget(possibleTarget);
+      }
+    }
+
+    private void addClassInitializerTarget(DexType type) {
+      if (type.isArrayType()) {
+        type = type.toBaseType(appInfo.dexItemFactory);
+      }
+      DexClass clazz = appInfo.definitionFor(type);
+      if (clazz != null) {
+        addClassInitializerTarget(clazz);
+      }
+    }
+
+    private void addTarget(DexEncodedMethod target) {
+      Node callee = graph.ensureMethodNode(target);
+      graph.addCall(caller, callee);
+    }
+
+    private void addPossibleTarget(DexEncodedMethod possibleTarget) {
+      DexClass possibleTargetClass =
+          appInfo.definitionFor(possibleTarget.method.getHolder());
+      if (possibleTargetClass != null && !possibleTargetClass.isLibraryClass()) {
+        addTarget(possibleTarget);
+      }
+    }
+
+    private void addPossibleTargets(
+        DexEncodedMethod definition, Set<DexEncodedMethod> possibleTargets) {
+      for (DexEncodedMethod possibleTarget : possibleTargets) {
+        if (possibleTarget != definition) {
+          addPossibleTarget(possibleTarget);
+        }
+      }
+    }
+
+    private void processInvoke(Type type, DexMethod method) {
+      DexEncodedMethod source = caller.method;
       method = graphLense.lookupMethod(method, source);
       DexEncodedMethod definition = appInfo.lookup(type, method);
       if (definition != null) {
         assert !source.accessFlags.isBridge() || definition != caller.method;
-        DexType definitionHolder = definition.method.getHolder();
-        assert definitionHolder.isClassType();
-        if (!appInfo.definitionFor(definitionHolder).isLibraryClass()) {
-          Node callee = graph.ensureMethodNode(definition);
-          graph.addCall(caller, callee);
+        DexClass definitionHolder = appInfo.definitionFor(definition.method.getHolder());
+        assert definitionHolder != null;
+        if (!definitionHolder.isLibraryClass()) {
+          addClassInitializerTarget(definitionHolder);
+          addTarget(definition);
           // For virtual and interface calls add all potential targets that could be called.
           if (type == Type.VIRTUAL || type == Type.INTERFACE) {
             Set<DexEncodedMethod> possibleTargets;
@@ -346,73 +386,74 @@
             } else {
               possibleTargets = appInfo.lookupVirtualTargets(definition.method);
             }
-            for (DexEncodedMethod possibleTarget : possibleTargets) {
-              if (possibleTarget != definition) {
-                DexClass possibleTargetClass =
-                    appInfo.definitionFor(possibleTarget.method.getHolder());
-                if (possibleTargetClass != null && !possibleTargetClass.isLibraryClass()) {
-                  callee = graph.ensureMethodNode(possibleTarget);
-                  graph.addCall(caller, callee);
-                }
-              }
-            }
+            addPossibleTargets(definition, possibleTargets);
           }
         }
       }
     }
 
+    private void processFieldAccess(DexField field) {
+      // Any field access implicitly calls the class initializer.
+      addClassInitializerTarget(field.getHolder());
+    }
+
     @Override
     public boolean registerInvokeVirtual(DexMethod method) {
-      processInvoke(caller.method, Type.VIRTUAL, method);
+      processInvoke(Type.VIRTUAL, method);
       return false;
     }
 
     @Override
     public boolean registerInvokeDirect(DexMethod method) {
-      processInvoke(caller.method, Type.DIRECT, method);
+      processInvoke(Type.DIRECT, method);
       return false;
     }
 
     @Override
     public boolean registerInvokeStatic(DexMethod method) {
-      processInvoke(caller.method, Type.STATIC, method);
+      processInvoke(Type.STATIC, method);
       return false;
     }
 
     @Override
     public boolean registerInvokeInterface(DexMethod method) {
-      processInvoke(caller.method, Type.INTERFACE, method);
+      processInvoke(Type.INTERFACE, method);
       return false;
     }
 
     @Override
     public boolean registerInvokeSuper(DexMethod method) {
-      processInvoke(caller.method, Type.SUPER, method);
+      processInvoke(Type.SUPER, method);
       return false;
     }
 
     @Override
     public boolean registerInstanceFieldWrite(DexField field) {
+      processFieldAccess(field);
       return false;
     }
 
     @Override
     public boolean registerInstanceFieldRead(DexField field) {
+      processFieldAccess(field);
       return false;
     }
 
     @Override
     public boolean registerNewInstance(DexType type) {
+      addClassInitializerTarget(type);
       return false;
     }
 
     @Override
     public boolean registerStaticFieldRead(DexField field) {
+      processFieldAccess(field);
       return false;
     }
 
     @Override
     public boolean registerStaticFieldWrite(DexField field) {
+      processFieldAccess(field);
       return false;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 23b6b70..3cd40f7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -463,6 +463,7 @@
     codeRewriter.foldConstants(code);
     codeRewriter.rewriteSwitch(code);
     codeRewriter.simplifyIf(code);
+    codeRewriter.collectClassInitializerDefaults(method, code);
     if (Log.ENABLED) {
       Log.debug(getClass(), "Intermediate (SSA) flow graph for %s:\n%s",
           method.toSourceString(), code);
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 484354a..8d36ef7 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
@@ -258,23 +258,15 @@
   @Override
   public void buildPrelude(IRBuilder builder) {
     Map<Integer, MoveType> initializedLocals = new HashMap<>(node.localVariables.size());
+    // Record types for arguments.
+    recordArgumentTypes(initializedLocals);
+    // Add debug information for all locals at the initial label.
     if (initialLabel != null) {
       state.openLocals(initialLabel);
     }
-    int argumentRegister = 0;
-    if (!isStatic()) {
-      Type thisType = Type.getType(clazz.descriptor.toString());
-      int register = state.writeLocal(argumentRegister++, thisType);
-      builder.addThisArgument(register);
-      initializedLocals.put(register, moveType(thisType));
-    }
-    for (Type type : parameterTypes) {
-      MoveType moveType = moveType(type);
-      int register = state.writeLocal(argumentRegister, type);
-      builder.addNonThisArgument(register, moveType);
-      argumentRegister += moveType.requiredRegisters();
-      initializedLocals.put(register, moveType);
-    }
+    // Build the actual argument instructions now that type and debug information is known
+    // for arguments.
+    buildArgumentInstructions(builder);
     if (isSynchronized()) {
       generatingMethodSynchronization = true;
       Type clazzType = Type.getType(clazz.toDescriptorString());
@@ -297,6 +289,23 @@
     for (Object o : node.localVariables) {
       LocalVariableNode local = (LocalVariableNode) o;
       Type localType = Type.getType(local.desc);
+      int sort = localType.getSort();
+      switch (sort) {
+        case Type.OBJECT:
+        case Type.ARRAY:
+          localType = JarState.NULL_TYPE;
+          break;
+        case Type.DOUBLE:
+        case Type.LONG:
+        case Type.FLOAT:
+          break;
+        case Type.VOID:
+        case Type.METHOD:
+          throw new Unreachable("Invalid local variable type: " + localType);
+        default:
+          localType = Type.INT_TYPE;
+          break;
+      }
       int localRegister = state.getLocalRegister(local.index, localType);
       MoveType exitingLocalType = initializedLocals.get(localRegister);
       assert exitingLocalType == null || exitingLocalType == moveType(localType);
@@ -311,6 +320,36 @@
     state.setBuilding();
   }
 
+  private void buildArgumentInstructions(IRBuilder builder) {
+    int argumentRegister = 0;
+    if (!isStatic()) {
+      Type thisType = Type.getType(clazz.descriptor.toString());
+      Slot slot = state.readLocal(argumentRegister++, thisType);
+      builder.addThisArgument(slot.register);
+    }
+    for (Type type : parameterTypes) {
+      MoveType moveType = moveType(type);
+      Slot slot = state.readLocal(argumentRegister, type);
+      builder.addNonThisArgument(slot.register, moveType);
+      argumentRegister += moveType.requiredRegisters();
+    }
+  }
+
+  private void recordArgumentTypes(Map<Integer, MoveType> initializedLocals) {
+    int argumentRegister = 0;
+    if (!isStatic()) {
+      Type thisType = Type.getType(clazz.descriptor.toString());
+      int register = state.writeLocal(argumentRegister++, thisType);
+      initializedLocals.put(register, moveType(thisType));
+    }
+    for (Type type : parameterTypes) {
+      MoveType moveType = moveType(type);
+      int register = state.writeLocal(argumentRegister, type);
+      argumentRegister += moveType.requiredRegisters();
+      initializedLocals.put(register, moveType);
+    }
+  }
+
   private void computeBlockEntryJarStates(IRBuilder builder) {
     Int2ReferenceSortedMap<BlockInfo> CFG = builder.getCFG();
     Queue<JarStateWorklistItem> worklist = new LinkedList<>();
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 5009d5e..a6b1f2c 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
@@ -87,6 +87,9 @@
       if (type == BYTE_OR_BOOL_TYPE) {
         type = Type.BYTE_TYPE;
       }
+      if (other == BYTE_OR_BOOL_TYPE) {
+        other = Type.BYTE_TYPE;
+      }
       int sort = type.getSort();
       int otherSort = other.getSort();
       if (isReferenceCompatible(type, other)) {
@@ -256,7 +259,7 @@
     Collection<LocalVariableNode> nodes = localVariableStartPoints.get(label);
     ArrayList<Local> locals = new ArrayList<>(nodes.size());
     for (LocalVariableNode node : nodes) {
-      locals.add(setLocal(node.index, Type.getType(node.desc), localVariables.get(node)));
+      locals.add(setLocalInfo(node.index, Type.getType(node.desc), localVariables.get(node)));
     }
     // Sort to ensure deterministic instruction ordering (correctness is unaffected).
     locals.sort(Comparator.comparingInt(local -> local.slot.register));
@@ -335,6 +338,18 @@
     return local;
   }
 
+  private Local setLocalInfo(int index, Type type, DebugLocalInfo info) {
+    return setLocalInfoForRegister(getLocalRegister(index, type), type, info);
+  }
+
+  private Local setLocalInfoForRegister(int register, Type type, DebugLocalInfo info) {
+    Local existingLocal = getLocalForRegister(register);
+    Local local = new Local(existingLocal.slot, info);
+    locals[register] = local;
+    return local;
+  }
+
+
   public int writeLocal(int index, Type type) {
     assert nonNullType(type);
     Local local = getLocal(index, type);
@@ -344,8 +359,9 @@
     }
     // We cannot assume consistency for writes because we do not have complete information about the
     // scopes of locals. We assume the program to be verified and overwrite if the types mismatch.
-    if (local == null || (local.info == null && !typeEquals(local.slot.type, type))) {
-      local = setLocal(index, type, null);
+    if (local == null || !typeEquals(local.slot.type, type)) {
+      DebugLocalInfo info = local == null ? null : local.info;
+      local = setLocal(index, type, info);
     }
     return local.slot.register;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
index 27cf89a..9f833f0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
@@ -5,19 +5,25 @@
 
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.google.common.base.Equivalence;
+import java.util.Arrays;
+import java.util.Comparator;
 import java.util.List;
 
 class BasicBlockInstructionsEquivalence extends Equivalence<BasicBlock> {
-
+  private static final int UNKNOW_HASH = -1;
   private static final int MAX_HASH_INSTRUCTIONS = 5;
   private final RegisterAllocator allocator;
+  private final int[] hashes;
 
-  BasicBlockInstructionsEquivalence(RegisterAllocator allocator) {
+  BasicBlockInstructionsEquivalence(IRCode code, RegisterAllocator allocator) {
     this.allocator = allocator;
+    hashes = new int[code.getHighestBlockNumber() + 1];
+    Arrays.fill(hashes, UNKNOW_HASH);
   }
 
   private boolean hasIdenticalInstructions(BasicBlock first, BasicBlock second) {
@@ -60,8 +66,23 @@
     return hasIdenticalInstructions(a, b);
   }
 
+  void clearComputedHash(BasicBlock basicBlock) {
+    hashes[basicBlock.getNumber()] = UNKNOW_HASH;
+  }
+
   @Override
   protected int doHash(BasicBlock basicBlock) {
+    int hash = hashes[basicBlock.getNumber()];
+    if (hash != UNKNOW_HASH) {
+      assert hash == computeHash(basicBlock);
+      return hash;
+    }
+    hash = computeHash(basicBlock);
+    hashes[basicBlock.getNumber()] = hash;
+    return hash;
+  }
+
+  private int computeHash(BasicBlock basicBlock) {
     List<Instruction> instructions = basicBlock.getInstructions();
     int hash = instructions.size();
     for (int i = 0; i < instructions.size() && i < MAX_HASH_INSTRUCTIONS; i++) {
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 48a64b8..eab1dfa 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
@@ -6,13 +6,26 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue.DexValueBoolean;
+import com.android.tools.r8.graph.DexValue.DexValueByte;
+import com.android.tools.r8.graph.DexValue.DexValueChar;
+import com.android.tools.r8.graph.DexValue.DexValueDouble;
+import com.android.tools.r8.graph.DexValue.DexValueFloat;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueLong;
+import com.android.tools.r8.graph.DexValue.DexValueNull;
+import com.android.tools.r8.graph.DexValue.DexValueShort;
+import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.Binop;
@@ -55,6 +68,7 @@
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Maps;
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -605,6 +619,102 @@
     }
   }
 
+  public void collectClassInitializerDefaults(DexEncodedMethod method, IRCode code) {
+    if (!method.isClassInitializer()) {
+      return;
+    }
+
+    // Collect all static put which are dominated by the exit block, and not dominated by a
+    // static get.
+    // This does not check for instructions that can throw. However, as classes which throw in the
+    // class initializer are never visible to the program (see Java Virtual Machine Specification
+    // section 5.5, https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5), this
+    // does not matter (except maybe for removal of const-string instructions, but that is
+    // acceptable).
+    DominatorTree dominatorTree = new DominatorTree(code);
+    BasicBlock exit = code.getNormalExitBlock();
+    if (exit == null) {
+      return;
+    }
+    Map<DexField, StaticPut> puts = Maps.newIdentityHashMap();
+    for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
+      InstructionListIterator iterator = block.listIterator(block.getInstructions().size());
+      while (iterator.hasPrevious()) {
+        Instruction current = iterator.previous();
+        if (current.isStaticPut()) {
+          StaticPut put = current.asStaticPut();
+          DexField field = put.getField();
+          if (!(field.type.isPrimitiveType()
+              || field.type == dexItemFactory.stringType)
+              || field.getHolder() != method.method.getHolder()) {
+            continue;
+          }
+          if (put.inValue().isConstant()) {
+            // Collect put as a potential default value.
+            puts.put(put.getField(), put);
+          }
+        }
+        if (current.isStaticGet()) {
+          // If a static field is read, any collected potential default value cannot be a
+          // default value.
+          if (puts.containsKey(current.asStaticGet().getField())) {
+            puts.remove(current.asStaticGet().getField());
+          }
+        }
+      }
+    }
+
+    if (!puts.isEmpty()) {
+      // Set initial values for static fields from the definitive static put instructions collected.
+      for (StaticPut put : puts.values()) {
+        DexField field = put.getField();
+        DexEncodedField encodedField = appInfo.definitionFor(field);
+        if (field.type == dexItemFactory.stringType) {
+          if (put.inValue().getConstInstruction().isConstNumber()) {
+            assert put.inValue().getConstInstruction().asConstNumber().isZero();
+            encodedField.staticValue = DexValueNull.NULL;
+          } else {
+            ConstString cnst = put.inValue().getConstInstruction().asConstString();
+            encodedField.staticValue = new DexValueString(cnst.getValue());
+          }
+        } else {
+          ConstNumber cnst = put.inValue().getConstInstruction().asConstNumber();
+          if (field.type == dexItemFactory.booleanType) {
+            encodedField.staticValue = DexValueBoolean.create(cnst.getBooleanValue());
+          } else if (field.type == dexItemFactory.byteType) {
+            encodedField.staticValue = DexValueByte.create((byte) cnst.getIntValue());
+          } else if (field.type == dexItemFactory.shortType) {
+            encodedField.staticValue = DexValueShort.create((short) cnst.getIntValue());
+          } else if (field.type == dexItemFactory.intType) {
+            encodedField.staticValue = DexValueInt.create(cnst.getIntValue());
+          } else if (field.type == dexItemFactory.longType) {
+            encodedField.staticValue = DexValueLong.create(cnst.getLongValue());
+          } else if (field.type == dexItemFactory.floatType) {
+            encodedField.staticValue = DexValueFloat.create(cnst.getFloatValue());
+          } else if (field.type == dexItemFactory.doubleType) {
+            encodedField.staticValue = DexValueDouble.create(cnst.getDoubleValue());
+          } else if (field.type == dexItemFactory.charType) {
+            encodedField.staticValue = DexValueChar.create((char) cnst.getIntValue());
+          } else {
+             throw new Unreachable("Unexpected field type.");
+          }
+        }
+      }
+
+      // Remove the static put instructions now replaced by static filed initial values.
+      for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
+        InstructionListIterator iterator = block.listIterator();
+        while (iterator.hasNext()) {
+          Instruction current = iterator.next();
+          if (current.isStaticPut() && puts.values().contains(current.asStaticPut())) {
+            iterator.remove();
+          }
+        }
+      }
+
+    }
+  }
+
   /**
    * Due to inlining, we might see chains of casts on subtypes. It suffices to cast to the lowest
    * subtype, as that will fail if a cast on a supertype would have failed.
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 4ad05cc..a7d6e8d 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
@@ -173,7 +173,7 @@
    */
   private static void removeIdenticalPredecessorBlocks(IRCode code, RegisterAllocator allocator) {
     BasicBlockInstructionsEquivalence equivalence =
-        new BasicBlockInstructionsEquivalence(allocator);
+        new BasicBlockInstructionsEquivalence(code, allocator);
     // Locate one block at a time that has identical predecessors. Rewrite those predecessors and
     // then start over. Restarting when one blocks predecessors have been rewritten simplifies
     // the rewriting and reduces the size of the data structures.
@@ -194,6 +194,7 @@
             BasicBlock otherPred = block.getPredecessors().get(otherPredIndex);
             pred.clearCatchHandlers();
             pred.getInstructions().clear();
+            equivalence.clearComputedHash(pred);
             for (BasicBlock succ : pred.getSuccessors()) {
               succ.removePredecessor(pred);
             }
diff --git a/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java b/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java
index 8903525..d6848a4 100644
--- a/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java
@@ -13,10 +13,11 @@
     long result = 0;
     int shift = 0;
     for (int i = 1; i < numberOfBytes; i++) {
-      result |= ((long) ((file.get() & 0xFF))) << shift;
+      result |= ((long) (file.get() & 0xFF)) << shift;
       shift += 8;
     }
-    return result | (file.get() << shift);
+    // Let the last byte sign-extend into any remaining bytes.
+    return result | (((long) file.get()) << shift);
   }
 
   // Inspired by com.android.dex.EncodedValueCodec
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index ea81b10..8712b7a 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -109,7 +109,7 @@
     return join(collection, separator, BraceType.NONE);
   }
 
-  public static <T> String join(String separator, T... strings) {
+  public static String join(String separator, String... strings) {
     return join(Arrays.asList(strings), separator, BraceType.NONE);
   }
 
@@ -124,15 +124,15 @@
     return builder.toString();
   }
 
-  public static <T> String lines(T... lines) {
+  public static String lines(String... lines) {
     StringBuilder builder = new StringBuilder();
-    for (T line : lines) {
+    for (String line : lines) {
       builder.append(line).append(LINE_SEPARATOR);
     }
     return builder.toString();
   }
 
-  public static <T> String joinLines(T... lines) {
+  public static String joinLines(String... lines) {
     return join(LINE_SEPARATOR, lines);
   }
 
diff --git a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
index 72bbaed..2782c91 100644
--- a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
@@ -183,9 +183,9 @@
         "MethodStart:",
         ".line 1",
 
-        "LabelXStart:",
         "  ldc 0",
         "  istore 1",
+        "LabelXStart:",
         ".line 2",
         "  invokestatic Test/ensureLine()V",
         "LabelXEnd:",
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress64658224.java b/src/test/java/com/android/tools/r8/jasmin/Regress64658224.java
new file mode 100644
index 0000000..8c1df56
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress64658224.java
@@ -0,0 +1,50 @@
+// 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 Regress64658224 extends JasminTestBase {
+
+  @Test
+  public void testInvalidTypeInfoFromLocals() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    clazz.addStaticMethod("foo", ImmutableList.of("I"), "V",
+        ".limit stack 2",
+        ".limit locals 2",
+        ".var 1 is x Ljava/lang/Object; from L1 to L2",
+        "  aconst_null",
+        "  astore 1",
+        "L1:",
+        "  iload 0",
+        "  ifeq L3",
+        "L2:",
+        "  goto L5",
+        "L3:",
+        "  aload 1",
+        "  iconst_0",
+        "  aaload",
+        "  pop",
+        "L5:",
+        "  return");
+
+    clazz.addMainMethod(
+        ".limit stack 1",
+        ".limit locals 1",
+        "  ldc 2",
+        "  invokestatic Test/foo(I)V",
+        "  return");
+
+    String expected = "";
+    String javaResult = runOnJava(builder, clazz.name);
+    assertEquals(expected, javaResult);
+    String artResult = runOnArtD8(builder, clazz.name);
+    assertEquals(expected, artResult);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValues.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValues.java
new file mode 100644
index 0000000..085b069
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValues.java
@@ -0,0 +1,198 @@
+// 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.rewrite.staticvalues;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.InternalOptions;
+import org.junit.Test;
+
+public class StaticValues extends SmaliTestBase {
+
+  @Test
+  public void testAllTypes() {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    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("stringField", "Ljava/lang/String;");
+
+    builder.addStaticInitializer(
+        2,
+        "const               v0, 1",
+        "sput-byte           v0, LTest;->booleanField:Z",
+        "sput-byte           v0, LTest;->byteField:B",
+        "const               v0, 2",
+        "sput-short          v0, LTest;->shortField:S",
+        "const               v0, 3",
+        "sput                v0, LTest;->intField:I",
+        "const-wide          v0, 4",
+        "sput-wide           v0, LTest;->longField:J",
+        "const               v0, 0x40a00000",  // 5.0.
+        "sput                v0, LTest;->floatField:F",
+        "const-wide          v0, 0x4018000000000000L",  // 6.0.
+        "sput-wide           v0, LTest;->doubleField:D",
+        "const               v0, 0x37",  // ASCII 7.
+        "sput-char           v0, LTest;->charField:C",
+        "const-string        v0, \"8\"",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "return-void"
+    );
+    builder.addMainMethod(
+        3,
+        "sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "sget-boolean        v1, LTest;->booleanField:Z",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Z)V",
+        "sget-byte           v1, LTest;->byteField:B",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "sget-short          v1, LTest;->shortField:S",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "sget                v1, LTest;->intField:I",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "sget-wide           v1, LTest;->longField:J",
+        "invoke-virtual      { v0, v1, v2 }, Ljava/io/PrintStream;->println(J)V",
+        "sget                v1, LTest;->floatField:F",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(F)V",
+        "sget-wide           v1, LTest;->doubleField:D",
+        "invoke-virtual      { v0, v1, v2 }, Ljava/io/PrintStream;->println(D)V",
+        "sget-char           v1, LTest;->charField:C",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(C)V",
+        "sget-object         v1, LTest;->stringField:Ljava/lang/String;",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "return-void"
+    );
+
+    InternalOptions options = new InternalOptions();
+    DexApplication originalApplication = buildApplication(builder, options);
+    DexApplication processedApplication = processApplication(originalApplication, options);
+
+    DexInspector inspector = new DexInspector(processedApplication);
+    MethodSubject clinit = inspector.clazz("Test").clinit();
+    // The const-string and return-void instructions are left.
+    assertEquals(2, clinit.getMethod().getCode().asDexCode().instructions.length);
+
+    String result = runArt(processedApplication, options);
+
+    assertEquals("true\n1\n2\n3\n4\n5.0\n6.0\n7\n8\n", result);
+  }
+
+  @Test
+  public void getBeforePut() {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    builder.addStaticField("field1", "I", "1");
+    builder.addStaticField("field2", "I", "2");
+
+    builder.addStaticInitializer(
+        1,
+        "sget                v0, LTest;->field1:I",
+        "sput                v0, LTest;->field2:I",
+        "const               v0, 0",
+        "sput                v0, LTest;->field1:I",
+        "return-void"
+    );
+    builder.addMainMethod(
+        2,
+        "sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "sget                v1, LTest;->field1:I",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "sget                v1, LTest;->field2:I",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "return-void"
+    );
+
+    InternalOptions options = new InternalOptions();
+    DexApplication originalApplication = buildApplication(builder, options);
+    DexApplication processedApplication = processApplication(originalApplication, options);
+
+    DexInspector inspector = new DexInspector(processedApplication);
+    MethodSubject clinit = inspector.clazz("Test").clinit();
+    // Nothing changed in the class initializer.
+    assertEquals(5, clinit.getMethod().getCode().asDexCode().instructions.length);
+
+    String result = runArt(processedApplication, options);
+
+    assertEquals("0\n1\n", result);
+  }
+
+  @Test
+  public void nullString() {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    builder.addStaticField("stringField", "Ljava/lang/String;", "Hello");
+
+    builder.addStaticInitializer(
+        1,
+        "const               v0, 0",
+        "sput-object         v0, LTest;->stringField:Ljava/lang/String;",
+        "return-void"
+    );
+    builder.addMainMethod(
+        2,
+        "sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "sget-object         v1, LTest;->stringField:Ljava/lang/String;",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+        "return-void"
+    );
+
+    InternalOptions options = new InternalOptions();
+    DexApplication originalApplication = buildApplication(builder, options);
+    DexApplication processedApplication = processApplication(originalApplication, options);
+
+    DexInspector inspector = new DexInspector(processedApplication);
+    MethodSubject clinit = inspector.clazz("Test").clinit();
+    // The return-void instruction is left.
+    assertEquals(1, clinit.getMethod().getCode().asDexCode().instructions.length);
+
+    String result = runArt(processedApplication, options);
+
+    assertEquals("null\n", result);
+  }
+
+  @Test
+  public void fieldOnOtherClass() {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    builder.addStaticInitializer(
+        1,
+        "const               v0, 2",
+        "sput                v0, LOther;->field:I",
+        "return-void"
+    );
+    builder.addMainMethod(
+        2,
+        "sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "sget                v1, LOther;->field:I",
+        "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+        "return-void"
+    );
+
+    builder.addClass("Other");
+    builder.addStaticField("field", "I", "1");
+
+    InternalOptions options = new InternalOptions();
+    DexApplication originalApplication = buildApplication(builder, options);
+    DexApplication processedApplication = processApplication(originalApplication, options);
+
+    DexInspector inspector = new DexInspector(processedApplication);
+    MethodSubject clinit = inspector.clazz("Test").clinit();
+    // Nothing changed in the class initializer.
+    assertEquals(3, clinit.getMethod().getCode().asDexCode().instructions.length);
+
+    String result = runArt(processedApplication, options);
+
+    assertEquals("2\n", result);
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index 03df65c..4cb6736 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -126,7 +126,9 @@
 
   private static void inspectShaking1(PrintUsageInspector inspector) {
     assertTrue(inspector.clazz("shaking1.Unused").isPresent());
-    assertFalse(inspector.clazz("shaking1.Used").isPresent());
+    assertTrue(inspector.clazz("shaking1.Used").isPresent());
+    ClassSubject used = inspector.clazz("shaking1.Used").get();
+    assertTrue(used.method("void", "<clinit>", ImmutableList.of()));
   }
 
   private static void inspectShaking2(PrintUsageInspector inspector) {
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 f070130..ab9bf96 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -71,6 +71,10 @@
       this.returnType = returnType;
       this.parameterTypes = parameterTypes;
     }
+
+    public static MethodSignature staticInitializer(String clazz) {
+      return new MethodSignature(clazz, "<clinit>", "void", ImmutableList.of());
+    }
   }
 
   public static class SmaliBuilder {
@@ -210,6 +214,29 @@
       );
     }
 
+    public void addStaticField(String name, String type, String defaultValue) {
+      StringBuilder builder = new StringBuilder();
+      builder.append(".field static ");
+      builder.append(name);
+      builder.append(":");
+      builder.append(type);
+      if (defaultValue != null) {
+        builder.append(" = ");
+        if (type.equals("Ljava/lang/String;")) {
+          builder.append('"');
+          builder.append(defaultValue);
+          builder.append('"');
+        } else {
+          builder.append(defaultValue);
+        }
+      }
+      getSource(currentClassName).add(builder.toString());
+    }
+
+    public void addStaticField(String name, String type) {
+      addStaticField(name, type, null);
+    }
+
     public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
         int locals, String... instructions) {
       StringBuilder builder = new StringBuilder();
@@ -222,8 +249,30 @@
 
     public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
         int locals, String code) {
+      return addStaticMethod("", returnType, name, parameters, locals, code);
+    }
+
+    public MethodSignature addStaticInitializer(int locals, String... instructions) {
+      StringBuilder builder = new StringBuilder();
+      for (String instruction : instructions) {
+        builder.append(instruction);
+        builder.append("\n");
+      }
+      return addStaticInitializer(locals, builder.toString());
+    }
+
+    public MethodSignature addStaticInitializer(int locals, String code) {
+      return addStaticMethod("constructor", "void", "<clinit>", ImmutableList.of(), locals, code);
+    }
+
+    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) {
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index 61c3c14..16c791d 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -267,6 +267,10 @@
 
     public abstract MethodSubject method(String returnType, String name, List<String> parameters);
 
+    public MethodSubject clinit() {
+      return method("void", "<clinit>", ImmutableList.of());
+    }
+
     public MethodSubject method(MethodSignature signature) {
       return method(signature.type, signature.name, ImmutableList.copyOf(signature.parameters));
     }
diff --git a/tools/test.py b/tools/test.py
index 8e016c7..ccc36d8 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -58,6 +58,9 @@
   result.add_option('--disable_assertions',
       help="Disable assertions when running tests.",
       default=False, action='store_true')
+  result.add_option('--with_code_coverage',
+      help="Enable code coverage with Jacoco.",
+      default=False, action='store_true')
 
   return result.parse_args()
 
@@ -72,10 +75,12 @@
 
 def Main():
   (options, args) = ParseOptions()
-  gradle_args = ['cleanTest', 'test']
   if len(args) > 1:
     print("test.py takes at most one argument, the pattern for tests to run")
     return -1
+
+  gradle_args = []
+  # Set all necessary Gradle properties and options first.
   if options.verbose:
     gradle_args.append('-Pprint_test_stdout')
   if options.no_internal:
@@ -96,9 +101,8 @@
     gradle_args.append('-Pjctf_compile_only')
   if options.disable_assertions:
     gradle_args.append('-Pdisable_assertions')
-  if len(args) > 0:
-    gradle_args.append('--tests')
-    gradle_args.append(args[0])
+  if options.with_code_coverage:
+    gradle_args.append('-Pwith_code_coverage')
   if os.name == 'nt':
     # temporary hack
     gradle_args.append('-Pno_internal')
@@ -108,6 +112,19 @@
     gradle_args.append('jctfCommonJar')
     gradle_args.append('-x')
     gradle_args.append('jctfTestsClasses')
+
+  # Add Gradle tasks
+  gradle_args.append('cleanTest')
+  gradle_args.append('test')
+  if len(args) > 0:
+    # Test filtering. Must always follow the 'test' task.
+    gradle_args.append('--tests')
+    gradle_args.append(args[0])
+  if options.with_code_coverage:
+    # Create Jacoco report after tests.
+    gradle_args.append('jacocoTestReport')
+
+  # Now run tests on selected runtime(s).
   vms_to_test = [options.dex_vm] if options.dex_vm != "all" else ALL_ART_VMS
   for art_vm in vms_to_test:
     return_code = gradle.RunGradle(gradle_args + ['-Pdex_vm=%s' % art_vm],
diff --git a/tools/test_framework.py b/tools/test_framework.py
index b6f5a19..32f0bc7 100755
--- a/tools/test_framework.py
+++ b/tools/test_framework.py
@@ -43,7 +43,7 @@
           ' third_party/framework/framework*.jar.'
           ' Report Golem-compatible CodeSize and RunTimeRaw values.')
   parser.add_argument('--tool',
-      choices = ['dx', 'd8', 'd8-release', 'goyt'],
+      choices = ['dx', 'd8', 'd8-release', 'goyt', 'goyt-release'],
       required = True,
       help = 'Compiler tool to use.')
   parser.add_argument('--name',
@@ -65,13 +65,15 @@
 
   with utils.TempDir() as temp_dir:
 
-    if args.tool in ['dx', 'goyt']:
+    if args.tool in ['dx', 'goyt', 'goyt-release']:
       tool_args = ['--dex', '--output=' + temp_dir, '--multi-dex',
           '--min-sdk-version=' + MIN_SDK_VERSION]
 
-    if args.tool == 'goyt':
+    if args.tool.startswith('goyt'):
       tool_file = GOYT_EXE
       tool_args = ['--num-threads=8'] + tool_args
+      if args.tool == 'goyt-release':
+        tool_args.append('--no-locals')
     elif args.tool == 'dx':
       tool_file = DX_JAR
     else: