Eagerly process single normal successor during redundant field load elimination

This can significantly reduce the number of states to keep track of at a time.

Change-Id: Ie20d568ea8180120b0fb3c463331bb2894a71d1a
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index f8212bc..1b355e4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -188,8 +188,21 @@
     return successors.size() == 1;
   }
 
+  public boolean hasUniqueNormalSuccessor() {
+    return numberOfNormalSuccessors() == 1;
+  }
+
+  public boolean hasUniqueNormalSuccessorWithUniquePredecessor() {
+    return hasUniqueNormalSuccessor() && getUniqueNormalSuccessor().getPredecessors().size() == 1;
+  }
+
   public BasicBlock getUniqueSuccessor() {
-    assert hasUniquePredecessor();
+    assert hasUniqueSuccessor();
+    return successors.get(0);
+  }
+
+  public BasicBlock getUniqueNormalSuccessor() {
+    assert hasUniqueNormalSuccessor();
     return successors.get(0);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index dafe00e..4b50b6e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -165,144 +165,162 @@
       }
     }
 
-    for (BasicBlock block : code.topologicallySortedBlocks()) {
-      computeActiveStateOnBlockEntry(block);
-      removeDeadBlockExitStates(block, pendingNormalSuccessors);
-      InstructionListIterator it = block.listIterator(code);
-      while (it.hasNext()) {
-        Instruction instruction = it.next();
-        if (instruction.isFieldInstruction()) {
-          DexField field = instruction.asFieldInstruction().getField();
-          DexEncodedField definition = resolveField(field);
-          if (definition == null || definition.isVolatile()) {
-            killAllNonFinalActiveFields();
-            continue;
-          }
+    Set<BasicBlock> visited = Sets.newIdentityHashSet();
+    for (BasicBlock head : code.topologicallySortedBlocks()) {
+      if (!visited.add(head)) {
+        continue;
+      }
+      computeActiveStateOnBlockEntry(head);
+      removeDeadBlockExitStates(head, pendingNormalSuccessors);
+      BasicBlock block = head;
+      BasicBlock end = null;
+      do {
+        InstructionListIterator it = block.listIterator(code);
+        while (it.hasNext()) {
+          Instruction instruction = it.next();
+          if (instruction.isFieldInstruction()) {
+            DexField field = instruction.asFieldInstruction().getField();
+            DexEncodedField definition = resolveField(field);
+            if (definition == null || definition.isVolatile()) {
+              killAllNonFinalActiveFields();
+              continue;
+            }
 
-          if (instruction.isInstanceGet()) {
-            InstanceGet instanceGet = instruction.asInstanceGet();
-            if (instanceGet.outValue().hasLocalInfo()) {
-              continue;
-            }
-            Value object = instanceGet.object().getAliasedValue();
-            FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-            FieldValue replacement = activeState.getInstanceFieldValue(fieldAndObject);
-            if (replacement != null) {
-              replacement.eliminateRedundantRead(it, instanceGet);
-            } else {
-              activeState.putNonFinalInstanceField(
-                  fieldAndObject, new ExistingValue(instanceGet.value()));
-            }
-          } else if (instruction.isInstancePut()) {
-            InstancePut instancePut = instruction.asInstancePut();
-            // An instance-put instruction can potentially write the given field on all objects
-            // because of aliases.
-            killNonFinalActiveFields(instancePut);
-            // ... but at least we know the field value for this particular object.
-            Value object = instancePut.object().getAliasedValue();
-            FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-            ExistingValue value = new ExistingValue(instancePut.value());
-            if (isFinal(definition)) {
-              assert method.isInstanceInitializer() || verifyWasInstanceInitializer();
-              activeState.putFinalInstanceField(fieldAndObject, value);
-            } else {
-              activeState.putNonFinalInstanceField(fieldAndObject, value);
-            }
-          } else if (instruction.isStaticGet()) {
-            StaticGet staticGet = instruction.asStaticGet();
-            if (staticGet.outValue().hasLocalInfo()) {
-              continue;
-            }
-            FieldValue replacement = activeState.getStaticFieldValue(field);
-            if (replacement != null) {
-              replacement.eliminateRedundantRead(it, staticGet);
-            } else {
-              // A field get on a different class can cause <clinit> to run and change static
-              // field values.
-              killNonFinalActiveFields(staticGet);
-              FieldValue value = new ExistingValue(staticGet.value());
+            if (instruction.isInstanceGet()) {
+              InstanceGet instanceGet = instruction.asInstanceGet();
+              if (instanceGet.outValue().hasLocalInfo()) {
+                continue;
+              }
+              Value object = instanceGet.object().getAliasedValue();
+              FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+              FieldValue replacement = activeState.getInstanceFieldValue(fieldAndObject);
+              if (replacement != null) {
+                replacement.eliminateRedundantRead(it, instanceGet);
+              } else {
+                activeState.putNonFinalInstanceField(
+                    fieldAndObject, new ExistingValue(instanceGet.value()));
+              }
+            } else if (instruction.isInstancePut()) {
+              InstancePut instancePut = instruction.asInstancePut();
+              // An instance-put instruction can potentially write the given field on all objects
+              // because of aliases.
+              killNonFinalActiveFields(instancePut);
+              // ... but at least we know the field value for this particular object.
+              Value object = instancePut.object().getAliasedValue();
+              FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+              ExistingValue value = new ExistingValue(instancePut.value());
               if (isFinal(definition)) {
+                assert method.isInstanceInitializer() || verifyWasInstanceInitializer();
+                activeState.putFinalInstanceField(fieldAndObject, value);
+              } else {
+                activeState.putNonFinalInstanceField(fieldAndObject, value);
+              }
+            } else if (instruction.isStaticGet()) {
+              StaticGet staticGet = instruction.asStaticGet();
+              if (staticGet.outValue().hasLocalInfo()) {
+                continue;
+              }
+              FieldValue replacement = activeState.getStaticFieldValue(field);
+              if (replacement != null) {
+                replacement.eliminateRedundantRead(it, staticGet);
+              } else {
+                // A field get on a different class can cause <clinit> to run and change static
+                // field values.
+                killNonFinalActiveFields(staticGet);
+                FieldValue value = new ExistingValue(staticGet.value());
+                if (isFinal(definition)) {
+                  activeState.putFinalStaticField(field, value);
+                } else {
+                  activeState.putNonFinalStaticField(field, value);
+                }
+              }
+            } else if (instruction.isStaticPut()) {
+              StaticPut staticPut = instruction.asStaticPut();
+              // A field put on a different class can cause <clinit> to run and change static
+              // field values.
+              killNonFinalActiveFields(staticPut);
+              ExistingValue value = new ExistingValue(staticPut.value());
+              if (definition.isFinal()) {
+                assert method.isClassInitializer();
                 activeState.putFinalStaticField(field, value);
               } else {
                 activeState.putNonFinalStaticField(field, value);
               }
             }
-          } else if (instruction.isStaticPut()) {
-            StaticPut staticPut = instruction.asStaticPut();
-            // A field put on a different class can cause <clinit> to run and change static
-            // field values.
-            killNonFinalActiveFields(staticPut);
-            ExistingValue value = new ExistingValue(staticPut.value());
-            if (definition.isFinal()) {
-              assert method.isClassInitializer();
-              activeState.putFinalStaticField(field, value);
-            } else {
-              activeState.putNonFinalStaticField(field, value);
+          } else if (instruction.isInitClass()) {
+            InitClass initClass = instruction.asInitClass();
+            assert !initClass.outValue().hasAnyUsers();
+            DexType clazz = initClass.getClassValue();
+            if (activeState.isClassInitialized(clazz)) {
+              it.removeOrReplaceByDebugLocalRead();
             }
-          }
-        } else if (instruction.isInitClass()) {
-          InitClass initClass = instruction.asInitClass();
-          assert !initClass.outValue().hasAnyUsers();
-          DexType clazz = initClass.getClassValue();
-          if (activeState.isClassInitialized(clazz)) {
-            it.removeOrReplaceByDebugLocalRead();
-          }
-          activeState.markClassAsInitialized(clazz);
-        } else if (instruction.isMonitor()) {
-          if (instruction.asMonitor().isEnter()) {
+            activeState.markClassAsInitialized(clazz);
+          } else if (instruction.isMonitor()) {
+            if (instruction.asMonitor().isEnter()) {
+              killAllNonFinalActiveFields();
+            }
+          } else if (instruction.isInvokeDirect()) {
+            handleInvokeDirect(instruction.asInvokeDirect());
+          } else if (instruction.isInvokeMethod() || instruction.isInvokeCustom()) {
             killAllNonFinalActiveFields();
-          }
-        } else if (instruction.isInvokeDirect()) {
-          handleInvokeDirect(instruction.asInvokeDirect());
-        } else if (instruction.isInvokeMethod() || instruction.isInvokeCustom()) {
-          killAllNonFinalActiveFields();
-        } else if (instruction.isNewInstance()) {
-          NewInstance newInstance = instruction.asNewInstance();
-          if (newInstance.clazz.classInitializationMayHaveSideEffects(
-              appView,
-              // Types that are a super type of `context` are guaranteed to be initialized already.
-              type -> appView.isSubtype(context, type).isTrue(),
-              Sets.newIdentityHashSet())) {
-            killAllNonFinalActiveFields();
-          }
-        } else {
-          // If the current instruction could trigger a method invocation, it could also cause field
-          // values to change. In that case, it must be handled above.
-          assert !instruction.instructionMayTriggerMethodInvocation(appView, context);
+          } else if (instruction.isNewInstance()) {
+            NewInstance newInstance = instruction.asNewInstance();
+            if (newInstance.clazz.classInitializationMayHaveSideEffects(
+                appView,
+                // Types that are a super type of `context` are guaranteed to be initialized
+                // already.
+                type -> appView.isSubtype(context, type).isTrue(),
+                Sets.newIdentityHashSet())) {
+              killAllNonFinalActiveFields();
+            }
+          } else {
+            // If the current instruction could trigger a method invocation, it could also cause
+            // field values to change. In that case, it must be handled above.
+            assert !instruction.instructionMayTriggerMethodInvocation(appView, context);
 
-          // If this assertion fails for a new instruction we need to determine if that instruction
-          // has side-effects that can change the value of fields. If so, it must be handled above.
-          // If not, it can be safely added to the assert.
-          assert instruction.isArgument()
-                  || instruction.isArrayGet()
-                  || instruction.isArrayLength()
-                  || instruction.isArrayPut()
-                  || instruction.isAssume()
-                  || instruction.isBinop()
-                  || instruction.isCheckCast()
-                  || instruction.isConstClass()
-                  || instruction.isConstMethodHandle()
-                  || instruction.isConstMethodType()
-                  || instruction.isConstNumber()
-                  || instruction.isConstString()
-                  || instruction.isDebugInstruction()
-                  || instruction.isDexItemBasedConstString()
-                  || instruction.isGoto()
-                  || instruction.isIf()
-                  || instruction.isInstanceOf()
-                  || instruction.isInvokeMultiNewArray()
-                  || instruction.isInvokeNewArray()
-                  || instruction.isMoveException()
-                  || instruction.isNewArrayEmpty()
-                  || instruction.isNewArrayFilledData()
-                  || instruction.isReturn()
-                  || instruction.isSwitch()
-                  || instruction.isThrow()
-                  || instruction.isUnop()
-              : "Unexpected instruction of type " + instruction.getClass().getTypeName();
+            // If this assertion fails for a new instruction we need to determine if that
+            // instruction has side-effects that can change the value of fields. If so, it must be
+            // handled above. If not, it can be safely added to the assert.
+            assert instruction.isArgument()
+                    || instruction.isArrayGet()
+                    || instruction.isArrayLength()
+                    || instruction.isArrayPut()
+                    || instruction.isAssume()
+                    || instruction.isBinop()
+                    || instruction.isCheckCast()
+                    || instruction.isConstClass()
+                    || instruction.isConstMethodHandle()
+                    || instruction.isConstMethodType()
+                    || instruction.isConstNumber()
+                    || instruction.isConstString()
+                    || instruction.isDebugInstruction()
+                    || instruction.isDexItemBasedConstString()
+                    || instruction.isGoto()
+                    || instruction.isIf()
+                    || instruction.isInstanceOf()
+                    || instruction.isInvokeMultiNewArray()
+                    || instruction.isInvokeNewArray()
+                    || instruction.isMoveException()
+                    || instruction.isNewArrayEmpty()
+                    || instruction.isNewArrayFilledData()
+                    || instruction.isReturn()
+                    || instruction.isSwitch()
+                    || instruction.isThrow()
+                    || instruction.isUnop()
+                : "Unexpected instruction of type " + instruction.getClass().getTypeName();
+          }
         }
-      }
-      recordActiveStateOnBlockExit(block);
+        if (block.hasUniqueNormalSuccessorWithUniquePredecessor()) {
+          block = block.getUniqueNormalSuccessor();
+          assert !visited.contains(block);
+          visited.add(block);
+        } else {
+          end = block;
+          block = null;
+        }
+      } while (block != null);
+      assert end != null;
+      recordActiveStateOnBlockExit(end);
     }
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
@@ -421,7 +439,9 @@
 
   private void recordActiveStateOnBlockExit(BasicBlock block) {
     assert !activeStateAtExit.containsKey(block);
-    activeStateAtExit.put(block, activeState);
+    if (!activeState.isEmpty()) {
+      activeStateAtExit.put(block, activeState);
+    }
   }
 
   private void killAllNonFinalActiveFields() {
@@ -560,6 +580,22 @@
       return initializedClasses != null && initializedClasses.contains(clazz);
     }
 
+    public boolean isEmpty() {
+      return isEmpty(finalInstanceFieldValues)
+          && isEmpty(finalStaticFieldValues)
+          && isEmpty(initializedClasses)
+          && isEmpty(nonFinalInstanceFieldValues)
+          && isEmpty(nonFinalStaticFieldValues);
+    }
+
+    private static boolean isEmpty(Set<?> set) {
+      return set == null || set.isEmpty();
+    }
+
+    private static boolean isEmpty(Map<?, ?> map) {
+      return map == null || map.isEmpty();
+    }
+
     // If a field get instruction throws an exception it did not have an effect on the value of the
     // field. Therefore, when propagating across exceptional edges for a field get instruction we
     // have to exclude that field from the set of known field values.