tree-cfg: Fix ICE with switch stmt to unreachable opt and forced labels [PR95857]
authorJakub Jelinek <jakub@redhat.com>
Thu, 2 Jul 2020 09:38:20 +0000 (11:38 +0200)
committerJakub Jelinek <jakub@redhat.com>
Thu, 2 Jul 2020 09:38:20 +0000 (11:38 +0200)
The following testcase ICEs, because during the cfg cleanup, we see:
  switch (i$e_11) <default: <L12> [33.33%], case -3: <lab2> [33.33%], case 0: <L10> [33.33%], case 2: <lab2> [33.33%]>
...
lab2:
  __builtin_unreachable ();
where lab2 is FORCED_LABEL.  The way it works, we go through the case labels
and when we reach the first one that points to gimple_seq_unreachable*
basic block, we remove the edge (if any) from the switch bb to the bb
containing the label and bbs reachable only through that edge we've just
removed.  Once we do that, we must throw away all other cases that use
the same label (or some other labels from the same bb we've removed the edge
to and the bb).  To avoid quadratic behavior, this is not done by walking
all remaining cases immediately before removing, but only when processing
them later.
For normal labels this works, fine, if the label is in a deleted bb, it will
have NULL label_to_block and we handle that case, or, if the unreachable bb
has some other edge to it, only the edge will be removed and not the bb,
and again, find_edge will not find the edge and we only remove the case.
And if a label would be to some other block, that other block wouldn't have
been removed earlier because there would be still an edge from the switch
block.
Now, FORCED_LABEL (and I think DECL_NONLOCAL too) break this, because
those labels aren't removed, but instead moved to some surrounding basic
block.  So, when we later process those, when their gimple_seq_unreachable*
basic block is removed, label_to_block will return some unrelated block
(in the testcase the switch bb), so we decide to keep the case which doesn't
seem to be unreachable, but we don't really have an edge from the switch
block to the block the label got moved to.

I thought first about punting in gimple_seq_unreachable* on
FORCED_LABEL/DECL_NONLOCAL labels, but that might penalize even code that
doesn't care, so this instead just makes sure that for
FORCED_LABEL/DECL_NONLOCAL labels that are being removed (and thus moved
randomly) we remember in a hash_set the fact that those labels should be
treated as removed for the purpose of the optimization, and later on
handle those labels that way.

2020-07-02  Jakub Jelinek  <jakub@redhat.com>

PR tree-optimization/95857
* tree-cfg.c (group_case_labels_stmt): When removing an unreachable
base_bb, remember all forced and non-local labels on it and later
treat those as if they have NULL label_to_block.  Formatting fix.
Fix a comment typo.

* gcc.dg/pr95857.c: New test.

gcc/testsuite/gcc.dg/pr95857.c [new file with mode: 0644]
gcc/tree-cfg.c

diff --git a/gcc/testsuite/gcc.dg/pr95857.c b/gcc/testsuite/gcc.dg/pr95857.c
new file mode 100644 (file)
index 0000000..41506ea
--- /dev/null
@@ -0,0 +1,37 @@
+/* PR tree-optimization/95857 */
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+struct E { int e; };
+int bar (void), baz (void);
+void qux (void *);
+
+void
+foo (int x)
+{
+  struct E a = { 0 };
+  struct E i = { 0 };
+  qux (&&lab2);
+  if (baz ())
+    i.e = 1;
+  else
+    a.e = -2;
+  switch (a.e)
+    {
+    case -2:
+    lab1:
+      switch (i.e)
+       {
+       case -3:
+       case 2:
+         if (i.e-- != 2)
+           __builtin_unreachable ();
+       lab2:
+         baz ();
+         goto lab1;
+       case 0:
+         bar ();
+       }
+      break;
+    }
+}
index 7a1ac80c2ac5159f543e564927e959200c8be729..b4d0c6db238146bc9a1ebd3e39ccfdaa7097a38a 100644 (file)
@@ -1728,6 +1728,7 @@ group_case_labels_stmt (gswitch *stmt)
   int old_size = gimple_switch_num_labels (stmt);
   int i, next_index, new_size;
   basic_block default_bb = NULL;
+  hash_set<tree> *removed_labels = NULL;
 
   default_bb = gimple_switch_default_bb (cfun, stmt);
 
@@ -1744,8 +1745,11 @@ group_case_labels_stmt (gswitch *stmt)
       base_bb = label_to_block (cfun, CASE_LABEL (base_case));
 
       /* Discard cases that have the same destination as the default case or
-        whose destiniation blocks have already been removed as unreachable.  */
-      if (base_bb == NULL || base_bb == default_bb)
+        whose destination blocks have already been removed as unreachable.  */
+      if (base_bb == NULL
+         || base_bb == default_bb
+         || (removed_labels
+             && removed_labels->contains (CASE_LABEL (base_case))))
        {
          i++;
          continue;
@@ -1768,10 +1772,13 @@ group_case_labels_stmt (gswitch *stmt)
          /* Merge the cases if they jump to the same place,
             and their ranges are consecutive.  */
          if (merge_bb == base_bb
+             && (removed_labels == NULL
+                 || !removed_labels->contains (CASE_LABEL (merge_case)))
              && wi::to_wide (CASE_LOW (merge_case)) == bhp1)
            {
-             base_high = CASE_HIGH (merge_case) ?
-                 CASE_HIGH (merge_case) : CASE_LOW (merge_case);
+             base_high
+               = (CASE_HIGH (merge_case)
+                  ? CASE_HIGH (merge_case) : CASE_LOW (merge_case));
              CASE_HIGH (base_case) = base_high;
              next_index++;
            }
@@ -1792,7 +1799,29 @@ group_case_labels_stmt (gswitch *stmt)
        {
          edge base_edge = find_edge (gimple_bb (stmt), base_bb);
          if (base_edge != NULL)
-           remove_edge_and_dominated_blocks (base_edge);
+           {
+             for (gimple_stmt_iterator gsi = gsi_start_bb (base_bb);
+                  !gsi_end_p (gsi); gsi_next (&gsi))
+               if (glabel *stmt = dyn_cast <glabel *> (gsi_stmt (gsi)))
+                 {
+                   if (FORCED_LABEL (gimple_label_label (stmt))
+                       || DECL_NONLOCAL (gimple_label_label (stmt)))
+                     {
+                       /* Forced/non-local labels aren't going to be removed,
+                          but they will be moved to some neighbouring basic
+                          block. If some later case label refers to one of
+                          those labels, we should throw that case away rather
+                          than keeping it around and refering to some random
+                          other basic block without an edge to it.  */
+                       if (removed_labels == NULL)
+                         removed_labels = new hash_set<tree>;
+                       removed_labels->add (gimple_label_label (stmt));
+                     }
+                 }
+               else
+                 break;
+             remove_edge_and_dominated_blocks (base_edge);
+           }
          i = next_index;
          continue;
        }
@@ -1809,6 +1838,7 @@ group_case_labels_stmt (gswitch *stmt)
   if (new_size < old_size)
     gimple_switch_set_num_labels (stmt, new_size);
 
+  delete removed_labels;
   return new_size < old_size;
 }