[dart2js] Better dead phi removal

Remove dead phis including back-edges and cycles.

Add a postcondition validation for optimization phases.

Add post-phase validation test for dead-phi removal and dead-code elimination.

Bug: https://github.com/dart-lang/sdk/issues/48397

Change-Id: I178cda52dd165e825b9e2d9ae642c22162cee2e3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/232782
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Stephen Adams <sra@google.com>
diff --git a/pkg/compiler/lib/src/ssa/interceptor_finalizer.dart b/pkg/compiler/lib/src/ssa/interceptor_finalizer.dart
index 2c20593..b8dcd74 100644
--- a/pkg/compiler/lib/src/ssa/interceptor_finalizer.dart
+++ b/pkg/compiler/lib/src/ssa/interceptor_finalizer.dart
@@ -49,6 +49,9 @@
   }
 
   @override
+  bool validPostcondition(HGraph graph) => true;
+
+  @override
   visitBasicBlock(HBasicBlock node) {
     HInstruction instruction = node.first;
     while (instruction != null) {
diff --git a/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart b/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart
index b52be9d..c474639 100644
--- a/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart
+++ b/pkg/compiler/lib/src/ssa/interceptor_simplifier.dart
@@ -47,6 +47,9 @@
   }
 
   @override
+  bool validPostcondition(HGraph graph) => true;
+
+  @override
   void visitBasicBlock(HBasicBlock node) {
     currentBlock = node;
 
diff --git a/pkg/compiler/lib/src/ssa/optimize.dart b/pkg/compiler/lib/src/ssa/optimize.dart
index fe51fdd..d7433a0 100644
--- a/pkg/compiler/lib/src/ssa/optimize.dart
+++ b/pkg/compiler/lib/src/ssa/optimize.dart
@@ -39,12 +39,14 @@
 import 'nodes.dart';
 import 'types.dart';
 import 'types_propagation.dart';
+import 'validate.dart' show NoUnusedPhiValidator;
 import 'value_range_analyzer.dart';
 import 'value_set.dart';
 
 abstract class OptimizationPhase {
   String get name;
   void visitGraph(HGraph graph);
+  bool validPostcondition(HGraph graph);
 }
 
 class SsaOptimizerTask extends CompilerTask {
@@ -70,6 +72,8 @@
       measureSubtask(phase.name, () => phase.visitGraph(graph));
       codegen.tracer.traceGraph(phase.name, graph);
       assert(graph.isValid(), 'Graph not valid after ${phase.name}');
+      assert(phase.validPostcondition(graph),
+          'Graph does not satify phase postcondition after ${phase.name}');
     }
 
     SsaCodeMotion codeMotion;
@@ -244,6 +248,9 @@
   }
 
   @override
+  bool validPostcondition(HGraph graph) => true;
+
+  @override
   visitBasicBlock(HBasicBlock block) {
     simplifyPhis(block);
     HInstruction instruction = block.first;
@@ -2518,6 +2525,11 @@
   }
 
   @override
+  bool validPostcondition(HGraph graph) {
+    return NoUnusedPhiValidator.containsNoUnusedPhis(graph);
+  }
+
+  @override
   void visitBasicBlock(HBasicBlock block) {
     bool isDeadBlock = analyzer.isDeadBlock(block);
     block.isLive = !isDeadBlock;
@@ -2827,27 +2839,33 @@
       }
     }
 
-    // Remove phis that are not live.
-    // Traverse in reverse order to remove phis with no uses before the
-    // phis that they might use.
-    // NOTICE: Doesn't handle circular references, but we don't currently
-    // create any.
-    List<HBasicBlock> blocks = graph.blocks;
-    for (int i = blocks.length - 1; i >= 0; i--) {
-      HBasicBlock block = blocks[i];
-      HPhi current = block.phis.first;
-      HPhi next = null;
-      while (current != null) {
-        next = current.next;
-        if (!livePhis.contains(current)
-            // TODO(ahe): Not sure the following is correct.
-            &&
-            current.usedBy.isEmpty) {
-          block.removePhi(current);
-        }
-        current = next;
-      }
+    // Collect dead phis.
+    List<HPhi> deadPhis = [];
+    for (final block in graph.blocks) {
+      block.forEachPhi((phi) {
+        if (!livePhis.contains(phi)) deadPhis.add(phi);
+      });
     }
+
+    // Two-phase removal, phase 1: unlink all the input nodes.
+    for (final phi in deadPhis) {
+      for (final input in phi.inputs) {
+        input.removeUser(phi);
+      }
+      phi.inputs.clear();
+    }
+
+    // Two-phase removal, phase 2: remove the now-disconnected phis.
+    for (final phi in deadPhis) {
+      assert(phi.inputs.isEmpty);
+      assert(phi.usedBy.isEmpty);
+      phi.block.removePhi(phi);
+    }
+  }
+
+  @override
+  bool validPostcondition(HGraph graph) {
+    return NoUnusedPhiValidator.containsNoUnusedPhis(graph);
   }
 }
 
@@ -2903,6 +2921,9 @@
       }
     }
   }
+
+  @override
+  bool validPostcondition(HGraph graph) => true;
 }
 
 class GvnWorkItem {
@@ -2933,6 +2954,9 @@
     } while (!workQueue.isEmpty);
   }
 
+  @override
+  bool validPostcondition(HGraph graph) => true;
+
   void moveLoopInvariantCode(HGraph graph) {
     for (int i = graph.blocks.length - 1; i >= 0; i--) {
       HBasicBlock block = graph.blocks[i];
@@ -3158,6 +3182,9 @@
   }
 
   @override
+  bool validPostcondition(HGraph graph) => true;
+
+  @override
   void visitBasicBlock(HBasicBlock block) {
     List<HBasicBlock> successors = block.successors;
 
@@ -3261,6 +3288,9 @@
     visitDominatorTree(graph);
   }
 
+  @override
+  bool validPostcondition(HGraph graph) => true;
+
   // Update users of [input] that are dominated by [:dominator.first:]
   // to use [TypeKnown] of [input] instead. As the type information depends
   // on the control flow, we mark the inserted [HTypeKnown] nodes as
@@ -3444,6 +3474,9 @@
   }
 
   @override
+  bool validPostcondition(HGraph graph) => true;
+
+  @override
   void visitBasicBlock(HBasicBlock block) {
     final predecessors = block.predecessors;
     final indegree = predecessors.length;
diff --git a/pkg/compiler/lib/src/ssa/types_propagation.dart b/pkg/compiler/lib/src/ssa/types_propagation.dart
index a8ff036..5a88ac3 100644
--- a/pkg/compiler/lib/src/ssa/types_propagation.dart
+++ b/pkg/compiler/lib/src/ssa/types_propagation.dart
@@ -71,6 +71,9 @@
   }
 
   @override
+  bool validPostcondition(HGraph graph) => true;
+
+  @override
   visitBasicBlock(HBasicBlock block) {
     if (block.isLoopHeader()) {
       block.forEachPhi((HPhi phi) {
diff --git a/pkg/compiler/lib/src/ssa/validate.dart b/pkg/compiler/lib/src/ssa/validate.dart
index 251368b..04c7f00 100644
--- a/pkg/compiler/lib/src/ssa/validate.dart
+++ b/pkg/compiler/lib/src/ssa/validate.dart
@@ -220,3 +220,28 @@
     }
   }
 }
+
+/// Validate that the graph contains no unused phi nodes.
+///
+///     assert(NoUnusedPhiValidator.containsNoUnusedPhis(graph));
+class NoUnusedPhiValidator extends HGraphVisitor {
+  bool isValid = true;
+
+  static bool containsNoUnusedPhis(HGraph graph) {
+    final validator = NoUnusedPhiValidator();
+    validator.visitDominatorTree(graph);
+    return validator.isValid;
+  }
+
+  @override
+  void visitBasicBlock(HBasicBlock block) {
+    block.forEachPhi(visitPhi);
+  }
+
+  void visitPhi(HPhi phi) {
+    if (phi.usedBy.isEmpty) {
+      print('Unused $phi in B${phi.block.id}');
+      isValid = false;
+    }
+  }
+}
diff --git a/pkg/compiler/lib/src/ssa/value_range_analyzer.dart b/pkg/compiler/lib/src/ssa/value_range_analyzer.dart
index 2b20fa4..d84928d 100644
--- a/pkg/compiler/lib/src/ssa/value_range_analyzer.dart
+++ b/pkg/compiler/lib/src/ssa/value_range_analyzer.dart
@@ -665,6 +665,9 @@
     optimizer.ranges = ranges;
   }
 
+  @override
+  bool validPostcondition(HGraph graph) => true;
+
   void removeRangeConversion() {
     conversions.forEach((HRangeConversion instruction) {
       instruction.block.rewrite(instruction, instruction.inputs[0]);