Fix redundant reprocessing of blocks in dead code remover

Bug: b/422947619
Change-Id: I6da6ffe13b401e7977cfb2358e62f8a161698cd1
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldGet.java b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
index 5a42ddc..4cd24cc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldGet.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 
-public interface FieldGet {
+public interface FieldGet extends InstructionOrValue {
 
   DexField getField();
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldPut.java b/src/main/java/com/android/tools/r8/ir/code/FieldPut.java
index 91e6d2d..37b1cf5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldPut.java
@@ -10,9 +10,7 @@
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 
-public interface FieldPut {
-
-  BasicBlock getBlock();
+public interface FieldPut extends InstructionOrValue {
 
   DexField getField();
 
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 1de0af6..003e17f 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
@@ -374,12 +374,9 @@
     }
   }
 
-  /**
-   * Returns the basic block containing this instruction.
-   */
+  /** Returns the basic block containing this instruction. */
   @Override
-  public BasicBlock getBlock() {
-    assert block != null;
+  public BasicBlock getBlockOrNull() {
     return block;
   }
 
@@ -423,13 +420,6 @@
     block.getInstructions().replace(this, newInstruction, affectedValues);
   }
 
-  /**
-   * Returns true if the instruction is in the IR and therefore has a block.
-   */
-  public boolean hasBlock() {
-    return block != null;
-  }
-
   public String getInstructionName() {
     return getClass().getSimpleName();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionOrValue.java b/src/main/java/com/android/tools/r8/ir/code/InstructionOrValue.java
index b45fd92..65c63f4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionOrValue.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionOrValue.java
@@ -29,5 +29,14 @@
     return null;
   }
 
-  BasicBlock getBlock();
+  default boolean hasBlock() {
+    return getBlockOrNull() != null;
+  }
+
+  default BasicBlock getBlock() {
+    assert hasBlock();
+    return getBlockOrNull();
+  }
+
+  BasicBlock getBlockOrNull();
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index e49e987..83dd50b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -84,12 +84,7 @@
   }
 
   @Override
-  public boolean hasBlock() {
-    return block != null;
-  }
-
-  @Override
-  public BasicBlock getBlock() {
+  public BasicBlock getBlockOrNull() {
     return block;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index fff0036..6546ce4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -1087,13 +1087,9 @@
         && !appView.enableWholeProgramOptimizations();
   }
 
-  public boolean hasBlock() {
-    return definition.hasBlock();
-  }
-
   @Override
-  public BasicBlock getBlock() {
-    return definition.getBlock();
+  public BasicBlock getBlockOrNull() {
+    return definition.getBlockOrNull();
   }
 
   public TypeElement getType() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index e459b66..cc09649 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -3,6 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_CUSTOM;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_MULTI_NEW_ARRAY;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_POLYMORPHIC;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
@@ -25,13 +35,12 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.WorkList;
 import com.android.tools.r8.utils.timing.Timing;
 import com.google.common.collect.ImmutableList;
-import java.util.ArrayDeque;
 import java.util.Collection;
-import java.util.Deque;
 import java.util.Iterator;
-import java.util.Queue;
+import java.util.List;
 
 public class DeadCodeRemover {
 
@@ -51,13 +60,15 @@
     // We may encounter unneeded catch handlers after each iteration, e.g., if a dead instruction
     // is the only throwing instruction in a block. Removing unneeded catch handlers can lead to
     // more dead instructions.
-    Deque<BasicBlock> worklist = new ArrayDeque<>();
+    List<BasicBlock> topologicallySortedBlocks = code.topologicallySortedBlocks();
+    WorkList<BasicBlock> worklist = WorkList.newIdentityWorkList();
     do {
       AffectedValues affectedValues = new AffectedValues();
       ValueIsDeadAnalysis valueIsDeadAnalysis = new ValueIsDeadAnalysis(appView, code);
-      worklist.addAll(code.topologicallySortedBlocks());
+      worklist.addIfNotSeen(topologicallySortedBlocks);
       while (!worklist.isEmpty()) {
         BasicBlock block = worklist.removeLast();
+        worklist.removeSeen(block);
         removeDeadInstructions(worklist, code, block, affectedValues, valueIsDeadAnalysis);
         removeDeadAndTrivialPhis(worklist, block, valueIsDeadAnalysis);
       }
@@ -95,30 +106,31 @@
   }
 
   // Add the block from where the value originates to the worklist.
-  private static void updateWorklist(Queue<BasicBlock> worklist, Value value) {
-    BasicBlock block = null;
-    if (value.isPhi()) {
-      block = value.asPhi().getBlock();
-    } else if (value.definition.hasBlock()) {
-      block = value.definition.getBlock();
-    }
+  private static void updateWorklist(WorkList<BasicBlock> worklist, Value value) {
+    BasicBlock block = value.getBlockOrNull();
     if (block != null) {
-      worklist.add(block);
+      worklist.addIfNotSeen(block);
     }
   }
 
   // Add all blocks from where the in/debug-values to the instruction originates.
-  private static void updateWorklist(Queue<BasicBlock> worklist, Instruction instruction) {
+  private static void updateWorklist(WorkList<BasicBlock> worklist, Instruction instruction) {
     for (Value inValue : instruction.inValues()) {
-      updateWorklist(worklist, inValue);
+      BasicBlock block = inValue.getBlockOrNull();
+      if (block != null && block != instruction.getBlock()) {
+        updateWorklist(worklist, inValue);
+      }
     }
     for (Value debugValue : instruction.getDebugValues()) {
-      updateWorklist(worklist, debugValue);
+      BasicBlock block = debugValue.getBlockOrNull();
+      if (block != null && block != instruction.getBlock()) {
+        updateWorklist(worklist, debugValue);
+      }
     }
   }
 
   private void removeDeadAndTrivialPhis(
-      Queue<BasicBlock> worklist, BasicBlock block, ValueIsDeadAnalysis valueIsDeadAnalysis) {
+      WorkList<BasicBlock> worklist, BasicBlock block, ValueIsDeadAnalysis valueIsDeadAnalysis) {
     Iterator<Phi> phiIt = block.getPhis().iterator();
     while (phiIt.hasNext()) {
       Phi phi = phiIt.next();
@@ -137,7 +149,7 @@
 
   @SuppressWarnings("ReferenceEquality")
   private void removeDeadInstructions(
-      Queue<BasicBlock> worklist,
+      WorkList<BasicBlock> worklist,
       IRCode code,
       BasicBlock block,
       AffectedValues affectedValues,
@@ -146,36 +158,54 @@
     while (iterator.hasPrevious()) {
       Instruction current = iterator.previous();
       if (current.hasOutValue()) {
-        // Replace unnecessary cast values.
-        if (current.isCheckCast()) {
-          CheckCast checkCast = current.asCheckCast();
-          if (!checkCast.isRefiningStaticType(appView.options())
-              && checkCast.outValue().getLocalInfo() == checkCast.object().getLocalInfo()) {
-            updateWorklistWithNonDebugUses(worklist, checkCast);
-            checkCast.outValue().replaceUsers(checkCast.object(), affectedValues);
-            checkCast.object().uniquePhiUsers().forEach(Phi::removeTrivialPhi);
-          }
-        }
-        // Remove unused invoke results.
-        if (current.isInvoke() && !current.outValue().isUsed()) {
-          current.setOutValue(null);
-        }
-        if (current.isStaticGet() && !current.outValue().isUsed() && appView.hasLiveness()) {
-          Box<InitClass> initClass = new Box<>();
-          if (iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
-              appView.withLiveness(),
-              code,
-              current.asStaticGet().getField().getHolderType(),
-              initClass::set)) {
-            if (initClass.isSet()) {
-              // Apply dead code remover to the new init-class instruction.
-              current = iterator.previous();
-              assert current == initClass.get();
-            } else {
-              // Instruction removed.
-              continue;
+        switch (current.opcode()) {
+          case CHECK_CAST:
+            {
+              // Replace unnecessary cast values.
+              CheckCast checkCast = current.asCheckCast();
+              if (!checkCast.isRefiningStaticType(appView.options())
+                  && checkCast.outValue().getLocalInfo() == checkCast.object().getLocalInfo()) {
+                updateWorklistWithNonDebugUses(worklist, checkCast);
+                checkCast.outValue().replaceUsers(checkCast.object(), affectedValues);
+                checkCast.object().uniquePhiUsers().forEach(Phi::removeTrivialPhi);
             }
+              break;
           }
+          case INVOKE_CUSTOM:
+          case INVOKE_DIRECT:
+          case INVOKE_INTERFACE:
+          case INVOKE_MULTI_NEW_ARRAY:
+          case INVOKE_STATIC:
+          case INVOKE_SUPER:
+          case INVOKE_POLYMORPHIC:
+          case INVOKE_VIRTUAL:
+            // Remove unused invoke results.
+            if (!current.outValue().isUsed()) {
+              current.clearOutValue();
+            }
+            break;
+          case STATIC_GET:
+            if (!current.outValue().isUsed() && appView.hasLiveness()) {
+              Box<InitClass> initClass = new Box<>();
+              if (iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
+                  appView.withLiveness(),
+                  code,
+                  current.asStaticGet().getField().getHolderType(),
+                  initClass::set)) {
+                if (initClass.isSet()) {
+                  // Apply dead code remover to the new init-class instruction.
+                  current = iterator.previous();
+                  assert current == initClass.get();
+                } else {
+                  // Instruction removed.
+                  continue;
+                }
+              }
+            }
+            break;
+          default:
+            assert !current.isInvoke();
+            break;
         }
       }
       DeadInstructionResult deadInstructionResult = current.canBeDeadCode(appView, code);
@@ -209,12 +239,12 @@
   }
 
   private static void updateWorklistWithNonDebugUses(
-      Queue<BasicBlock> worklist, CheckCast checkCast) {
+      WorkList<BasicBlock> worklist, CheckCast checkCast) {
     for (Instruction user : checkCast.outValue().uniqueUsers()) {
-      worklist.add(user.getBlock());
+      worklist.addIfNotSeen(user.getBlock());
     }
     for (Phi user : checkCast.outValue().uniquePhiUsers()) {
-      worklist.add(user.getBlock());
+      worklist.addIfNotSeen(user.getBlock());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index aac9d19..c9a17a0 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -167,6 +167,11 @@
     return next;
   }
 
+  public void removeSeen(T element) {
+    boolean removed = seen.remove(element);
+    assert removed;
+  }
+
   public void clearSeen() {
     seen.clear();
   }