On Thu, Jul 2, 2020 at 11:03 AM Jakub Jelinek via Gcc-patches
<gcc-patches@gcc.gnu.org> wrote:
>
> Hi!
>
> 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.
>
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?

OK.

Richard.

> 2020-07-02  Jakub Jelinek  <ja...@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/tree-cfg.c.jj   2020-06-30 11:18:52.981737596 +0200
> +++ gcc/tree-cfg.c      2020-06-30 12:50:22.068246981 +0200
> @@ -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;
>  }
>
> --- gcc/testsuite/gcc.dg/pr95857.c.jj   2020-06-30 12:48:53.574511328 +0200
> +++ gcc/testsuite/gcc.dg/pr95857.c      2020-06-30 12:48:53.574511328 +0200
> @@ -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;
> +    }
> +}
>
>         Jakub
>

Reply via email to