Merge "Mark byte-or-bool types when propagating type information."
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 9a367ff..bbf3d57 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -80,20 +80,27 @@
 
   @Override
   public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options) {
-    triggerDelayedParsingIfNeccessary();
-    JarSourceCode source = new JarSourceCode(clazz, node, application);
-    IRBuilder builder = new IRBuilder(encodedMethod, source, options);
-    return builder.build();
+    return internalBuild(encodedMethod, null, options);
   }
 
-  public IRCode buildIR(DexEncodedMethod encodedMethod, ValueNumberGenerator generator,
-      InternalOptions options) {
-    triggerDelayedParsingIfNeccessary();
-    JarSourceCode source = new JarSourceCode(clazz, node, application);
-    IRBuilder builder = new IRBuilder(encodedMethod, source, generator, options);
-    return builder.build();
+  public IRCode buildIR(
+      DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options) {
+    return internalBuild(encodedMethod, generator, options);
   }
 
+  private IRCode internalBuild(
+      DexEncodedMethod encodedMethod, ValueNumberGenerator generator, InternalOptions options) {
+    triggerDelayedParsingIfNeccessary();
+    if (!options.debug) {
+      node.localVariables.clear();
+    }
+    JarSourceCode source = new JarSourceCode(clazz, node, application);
+    IRBuilder builder =
+        (generator == null)
+            ? new IRBuilder(encodedMethod, source, options)
+            : new IRBuilder(encodedMethod, source, generator, options);
+    return builder.build();
+  }
 
   @Override
   public void registerReachableDefinitions(UseRegistry registry) {
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 12d8b6a..484354a 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
@@ -308,6 +308,7 @@
       }
     }
     computeBlockEntryJarStates(builder);
+    state.setBuilding();
   }
 
   private void computeBlockEntryJarStates(IRBuilder builder) {
@@ -360,6 +361,7 @@
         }
       }
     }
+    state.restoreState(0);
   }
 
   private void updateStateForLocalVariableEnd(AbstractInsnNode insn) {
@@ -1146,9 +1148,11 @@
         state.pop();
         Type elementType = state.pop(JarState.ARRAY_TYPE).getArrayElementType();
         if (elementType == null) {
-          // We propagate the null type, which will then get resolved to an
-          // actual type if we have a non-null type on another flow edge.
-          elementType = JarState.NULL_TYPE;
+          // We propagate the byte-or-bool type, which will then get resolved to an
+          // actual type if we have a concrete byte type or bool type on another flow edge.
+          elementType = (Opcodes.BALOAD == opcode)
+              ? JarState.BYTE_OR_BOOL_TYPE
+              : getArrayElementTypeForOpcode(opcode);
         }
         state.push(elementType);
         break;
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 9d11fae..ee5252d 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
@@ -36,6 +36,10 @@
   // Type representative for the null value (non-existent but works for tracking the types here).
   public static final Type NULL_TYPE = Type.getObjectType("<null>");
 
+  // TODO(zerny): Define an internal Type wrapping ASM types so that we can define an actual value.
+  // Type representative for a value that may be either a boolean or a byte.
+  public static final Type BYTE_OR_BOOL_TYPE = null;
+
   // Typed mapping from a local slot or stack slot to a virtual register.
   public static class Slot {
     public final int register;
@@ -78,6 +82,9 @@
       assert type != REFERENCE_TYPE;
       assert type != OBJECT_TYPE;
       assert type != ARRAY_TYPE;
+      if (type == BYTE_OR_BOOL_TYPE) {
+        type = Type.BYTE_TYPE;
+      }
       int sort = type.getSort();
       int otherSort = other.getSort();
       if (isReferenceCompatible(type, other)) {
@@ -183,6 +190,9 @@
 
   private final Map<Integer, Snapshot> targetStates = new HashMap<>();
 
+  // Mode denoting that the state setup is done and we are now emitting IR.
+  // Concretely we treat all remaining byte-or-bool types as bytes (no actual type can flow there).
+  private boolean building = false;
 
   public JarState(int maxLocals, Map<LocalVariableNode, DebugLocalInfo> localVariables) {
     int localsRegistersSize = maxLocals * 3;
@@ -205,6 +215,39 @@
     }
   }
 
+  public void setBuilding() {
+    assert stack.isEmpty();
+    building = true;
+    for (int i = 0; i < locals.length; i++) {
+      Local local = locals[i];
+      if (local != null && local.slot.type == BYTE_OR_BOOL_TYPE) {
+        locals[i] = new Local(new Slot(local.slot.register, Type.BYTE_TYPE), local.info);
+      }
+    }
+    for (Entry<Integer, Snapshot> entry : targetStates.entrySet()) {
+      Local[] locals = entry.getValue().locals;
+      for (int i = 0; i < locals.length; i++) {
+        Local local = locals[i];
+        if (local != null && local.slot.type == BYTE_OR_BOOL_TYPE) {
+          locals[i] = new Local(new Slot(local.slot.register, Type.BYTE_TYPE), local.info);
+        }
+      }
+      ImmutableList.Builder<Slot> builder = ImmutableList.builder();
+      boolean found = false;
+      for (Slot slot : entry.getValue().stack) {
+        if (slot.type == BYTE_OR_BOOL_TYPE) {
+          found = true;
+          builder.add(new Slot(slot.register, Type.BYTE_TYPE));
+        } else {
+          builder.add(slot);
+        }
+      }
+      if (found) {
+        entry.setValue(new Snapshot(locals, builder.build()));
+      }
+    }
+  }
+
   // Local variable procedures.
 
   public List<Local> openLocals(LabelNode label) {
@@ -253,6 +296,10 @@
 
   int getLocalRegister(int index, Type type) {
     assert index < localsSize;
+    if (type == BYTE_OR_BOOL_TYPE) {
+      assert Slot.isCategory1(type);
+      return index + localsSize;
+    }
     if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
       return index;
     }
@@ -287,17 +334,22 @@
   }
 
   public int writeLocal(int index, Type type) {
-    assert type != null;
+    assert nonNullType(type);
     Local local = getLocal(index, type);
     assert local == null || local.info == null || local.slot.isCompatibleWith(type);
     // 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 && !local.slot.type.equals(type))) {
+    if (local == null || (local.info == null && !typeEquals(local.slot.type, type))) {
       local = setLocal(index, type, null);
     }
     return local.slot.register;
   }
 
+  public boolean typeEquals(Type type1, Type type2) {
+    return (type1 == BYTE_OR_BOOL_TYPE && type2 == BYTE_OR_BOOL_TYPE)
+        || (type1 != null && type1.equals(type2));
+  }
+
   public Slot readLocal(int index, Type type) {
     Local local = getLocal(index, type);
     assert local != null;
@@ -305,10 +357,14 @@
     return local.slot;
   }
 
+  public boolean nonNullType(Type type) {
+    return type != null || !building;
+  }
+
   // Stack procedures.
 
   public int push(Type type) {
-    assert type != null;
+    assert nonNullType(type);
     int top = topOfStack;
     // For simplicity, every stack slot (and local variable) is wide (uses two registers).
     topOfStack += 2;
@@ -332,7 +388,7 @@
     // For simplicity, every stack slot (and local variable) is wide (uses two registers).
     topOfStack -= 2;
     Slot slot = stack.pop();
-    assert slot.type != null;
+    assert nonNullType(slot.type);
     assert slot.register == topOfStack;
     return slot;
   }
@@ -416,13 +472,17 @@
     return true;
   }
 
+  private boolean isRefinement(Type current, Type other) {
+    return (current == JarState.NULL_TYPE && other != JarState.NULL_TYPE)
+        || (current == JarState.BYTE_OR_BOOL_TYPE && other != JarState.BYTE_OR_BOOL_TYPE);
+  }
+
   private ImmutableList<Slot> mergeStacks(
       ImmutableList<Slot> currentStack, ImmutableList<Slot> newStack) {
     assert currentStack.size() == newStack.size();
     List<Slot> mergedStack = null;
     for (int i = 0; i < currentStack.size(); i++) {
-      if (currentStack.get(i).type == JarState.NULL_TYPE &&
-          newStack.get(i).type != JarState.NULL_TYPE) {
+      if (isRefinement(currentStack.get(i).type, newStack.get(i).type)) {
         if (mergedStack == null) {
           mergedStack = new ArrayList<>();
           mergedStack.addAll(currentStack.subList(0, i));
@@ -448,8 +508,7 @@
       // If this assert triggers we can get different debug information for the same local
       // on different control-flow paths and we will have to merge them.
       assert currentLocal.info == newLocal.info;
-      if (currentLocal.slot.type == JarState.NULL_TYPE &&
-          newLocal.slot.type != JarState.NULL_TYPE) {
+      if (isRefinement(currentLocal.slot.type, newLocal.slot.type)) {
         if (mergedLocals == null) {
           mergedLocals = new Local[currentLocals.length];
           System.arraycopy(currentLocals, 0, mergedLocals, 0, i);
@@ -463,15 +522,6 @@
     return mergedLocals != null ? mergedLocals : currentLocals;
   }
 
-  private static boolean verifyStack(List<Slot> stack, List<Slot> other) {
-    assert stack.size() == other.size();
-    int i = 0;
-    for (Slot slot : stack) {
-      assert slot.isCompatibleWith(other.get(i++).type);
-    }
-    return true;
-  }
-
   // Other helpers.
 
   private static boolean verifySlots(Slot[] slots, Type type) {
@@ -490,7 +540,11 @@
   public static String stackToString(Collection<Slot> stack) {
     List<String> strings = new ArrayList<>(stack.size());
     for (Slot slot : stack) {
-      strings.add(slot.type.toString());
+      if (slot.type == BYTE_OR_BOOL_TYPE) {
+        strings.add("<byte|bool>");
+      } else {
+        strings.add(slot.type.toString());
+      }
     }
     StringBuilder builder = new StringBuilder("{ ");
     for (int i = strings.size() - 1; i >= 0; i--) {
@@ -516,6 +570,8 @@
         builder.append("_");
       } else if (local.info != null) {
         builder.append(local.info);
+      } else if (local.slot.type == BYTE_OR_BOOL_TYPE) {
+        builder.append("<byte|bool>");
       } else {
         builder.append(local.slot.type.toString());
       }