On Thu, Jan 3, 2013 at 9:51 PM, Jeff Law wrote:
> On 01/03/2013 12:01 PM, Steven Bosscher wrote:
>>
>> Hello,
>>
>> Consider the following test case:
>>
>> void bar (void);
>> int foo (int b, int c, int d)
>> {
>>    int r = 0;
>>    if (b)
>>      res = b * 2 + 4;
>>    if (c)
>>      {
>>        if (d)
>>          r = res;
>>        else
>>          __builtin_unreachable ();
>>      }
>>    return r;
>> }
>>
>> This is typical for code in GCC itself in places where
>> gcc_unreachable() is used.
>
>
>>
>> The corresponding CFG looks like this:
>>
>>              +-----+
>>              | bb0 |
>>              +-----+
>>                |
>>                |
>>                v
>>              +-----+
>>              | bb2 | -+
>>              +-----+  |
>>                |      |
>>                |      |
>>                v      |
>>              +-----+  |
>>              | bb3 |  |
>>              +-----+  |
>>                |      |
>>                |      |
>>                v      |
>> +-----+     +-----+  |
>> | bb8 | <-- | bb4 | <+
>> +-----+     +-----+
>>    |           |
>>    |           |
>>    |           v
>>    |         +-----+     +-----+
>>    |         | bb5 | --> | bb7 |
>>    |         +-----+     +-----+
>>    |           |
>>    |           |
>>    |           v
>>    |         +-----+
>>    |         | bb6 |
>>    |         +-----+
>>    |           |
>>    |           |
>>    |           v
>>    |         +-----+
>>    +-------> | bb9 |
>>              +-----+
>>                |
>>                |
>>                v
>>              +-----+
>>              | bb1 |
>>              +-----+
>
> Presumably BB7 was created in response to the builtin_unreachable?

Yes. The block only contains the BB_UNREACHABLE call. It is cleaned up
at the end of the GIMPLE passes pipeline, in the fold-all-builtins
pass (most __builtin_unreachable calls are, but not all).


> One
> could argue that an empty dead-end basic block should just be removed and
> the CFG appropriately simplified.

The problem with this, is that __builtin_unreachable actually exposes
optimization opportunities: more const/copy props of "implicit sets"
in the predicate guarding the __builtin_unreachable call, more
optimistic value numbering, etc. It also helps improve maybe-unused
warnings accuracy. So simply removing these "dead ends" in the CFG is
probably not a good idea.


> You might want to look  at a discussion from Oct/Nov 2011 "New pass to
> delete unexecutable paths in the CFG" which touches on some of this stuff.

That's a really interesting discussion! I must have missed it at the time :-)


> It's not 100% the same, but the concept of eliminating edges from the CFG
> which we can never traverse in a conforming program applies to both your
> example and the stuff I was playing with.

I think there is one important difference: In the thread you referred
to, you're removing paths in the CFG that are implicitly not
executable (for some languages anyway), whereas a
__builtin_unreachable call is an explicit marker for "this can never
happen". I think this difference is important:

* The explicit marker may have been put there on purpose (e.g. to get
rid of a false-positive warning). The compiler should respect that. An
implicit unreachable path can be optimized away without regard for the
user's intent.

* The explicit marker should not inhibit optimizations. For an
implicit unreachable path the compiler should be conservative. But for
a __builtin_unreachable call that is the only statement in a basic
block, the compiler should be allowed to work as if the block really
is never reached.

The attached patch implements these ideas. During a tree-CFG cleanup,
basic blocks containing only a __builtin_unreachable call are marked
with a new flag BB_NEVER_REACHED. The flag is used in post-dominance:
A marked block is not considered in the computations of the immediate
post-dominator of the predecessor blocks. The result is a more
optimistic post-dominance tree: Without the patch all predecessors of
these BB_NEVER_REACHED blocks have the EXIT block as their
post-dominator, but with the patch this non-executable path in the CFG
is ignored and the post-dominators are those that would result if the
BB_NEVER_REACHED blocks are not there at all (the BB_NEVER_REACHED
blocks themselves are only post-dominated by the EXIT block).

I've also added a control dependence calculation function. It's not
currently used, but it shows how the BB_NEVER_REACHED flag is used in
this case to avoid the "false" control dependences that I showed in
the graphs in http://gcc.gnu.org/ml/gcc/2013-01/msg00021.html.

Bootstrapped&tested on powerpc64-unknown-linux-gnu.
What do you think of this approach?

Ciao!
Steven
        * cfg-flags.def (BB_NEVER_REACHED): New flag on basic_block.
        (BB_SUPERBLOCK): Add FIXME, document that this flag should be removed.
        * tree-cfgcleanup.c (is_only_builtin_unreachable_block): New function.
        (mark_builtin_unreachable_blocks): New function.
        (cleanup_tree_cfg_1): Call mark_builtin_unreachable_blocks at the end
        to set BB_NEVER_REACHED on basic blocks containing only a call to
        __builtin_unreachable.
        * cfgexpand.c (expand_gimple_basic_block): Clear BB_NEVER_REACHED.
        * dominance.c (calc_idoms): Do not mess with edge_iterator internals.
        If a basic block has BB_NEVER_REACHED set, and post-dominators are
        being computed, ignore the block for the computation of the immediate
        post-dominator of its predecessors.

        * basic-block.h (compute_control_dependences): New prototype.
        * cfganal.c (compute_dominance_frontiers_1): Implement dominance
        frontiers of the reverse CFG, aka control dependence.
        (compute_dominance_frontiers): Update for above change.
        (compute_control_dependences): New function.

Index: cfg-flags.def
===================================================================
--- cfg-flags.def       (revision 194924)
+++ cfg-flags.def       (working copy)
@@ -51,9 +51,26 @@ DEF_BASIC_BLOCK_FLAG(REACHABLE, 1)
 /* Set for blocks in an irreducible loop by loop analysis.  */
 DEF_BASIC_BLOCK_FLAG(IRREDUCIBLE_LOOP, 2)
 
-/* Set on blocks that may actually not be single-entry single-exit block.  */
+/* Set on blocks that may actually not be single-entry single-exit block.
+   Only valid in RTL.  The value of this flag is overloaded by the
+   BB_NEVER_REACHED flag.
+   FIXME: This is only used by the SLJL exception mechanism.  Should clean
+   this up for GCC 4.9.  */
 DEF_BASIC_BLOCK_FLAG(SUPERBLOCK, 3)
 
+/* Set on blocks that contain only a __builtin_unreachable call.  Such blocks
+   are not really reachable, their only purpose is to inform the compiler that
+   some path through the control flow graph can never be taken.  This flag is
+   used, for instance, to ignore marked basic blocks in the computation of
+   post-dominance.  This flag is only used in GIMPLE, __builtin_unreachable
+   calls are either removed during builtin-folding or expanded as BARRIERs.
+   Note that the meaning of this flag is *not* the complement of BB_REACHABLE.
+   The latter is set on blocks that are reachable from the CFG entry block.
+   The BB_NEVER_REACHED flag is only set on basic blocks that have been
+   marked expelicitly as unreachable using __builtin_unreachable, but such
+   blocks are usually reachable from ENTRY.  */
+DEF_BASIC_BLOCK_FLAG(NEVER_REACHED, 3)
+
 /* Set on basic blocks that the scheduler should not touch.  This is used
    by SMS to prevent other schedulers from messing with the loop schedule.  */
 DEF_BASIC_BLOCK_FLAG(DISABLE_SCHEDULE, 4)
Index: tree-cfgcleanup.c
===================================================================
--- tree-cfgcleanup.c   (revision 194924)
+++ tree-cfgcleanup.c   (working copy)
@@ -577,6 +577,55 @@ split_bbs_on_noreturn_calls (void)
   return changed;
 }
 
+/* Return true if B is empty except for a __builtin_unreachable call and
+   maybe debug statements and/or removable labels.  */
+
+static bool
+is_only_builtin_unreachable_block (basic_block bb)
+{
+  gimple_stmt_iterator gsi;
+
+  if (EDGE_COUNT (bb->succs) > 1
+      || (EDGE_COUNT (bb->succs) == 1
+         && !(single_succ_edge (bb)->flags & EDGE_FAKE)))
+    return false;
+  for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+    {
+      gimple stmt = gsi_stmt (gsi);
+      if (is_gimple_debug (stmt))
+       continue;
+      if (gimple_code (stmt) == GIMPLE_LABEL)
+       {
+         if (FORCED_LABEL (gimple_label_label (stmt)))
+           return false;
+         continue;
+       }
+      if (! is_gimple_builtin_call (stmt)
+         || DECL_FUNCTION_CODE (gimple_call_fndecl (stmt))
+            != BUILT_IN_UNREACHABLE)
+       return false;
+    }
+  return true;
+}
+
+/* Mark basic blocks that only contain a __builtin_unreachabel call as
+   BB_NEVER_REACHED.  Such blocks may appear reachable but, by definition,
+   really are not reachable.  Not marking these blocks is conservative but
+   may inhibit optimizations (e.g. post-dominance and control dependences
+   are pessimized if unmarked blocks persist).  */
+
+static void
+mark_builtin_unreachable_blocks (void)
+{
+  basic_block bb;
+  FOR_EACH_BB (bb)
+    {
+      bb->flags &= ~BB_NEVER_REACHED;
+      if (is_only_builtin_unreachable_block (bb))
+       bb->flags |= BB_NEVER_REACHED;
+    }
+}
+
 /* Tries to cleanup cfg in basic block BB.  Returns true if anything
    changes.  */
 
@@ -652,6 +701,7 @@ cleanup_tree_cfg_1 (void)
 
   end_recording_case_labels ();
   BITMAP_FREE (cfgcleanup_altered_bbs);
+  mark_builtin_unreachable_blocks ();
   return retval;
 }
 
Index: cfgexpand.c
===================================================================
--- cfgexpand.c (revision 194924)
+++ cfgexpand.c (working copy)
@@ -3776,6 +3776,9 @@ expand_gimple_basic_block (basic_block bb, bool di
     fprintf (dump_file, "\n;; Generating RTL for gimple basic block %d\n",
             bb->index);
 
+  /* This flag has no purpose in RTL land (and clashes with BB_SUPERBLOCK).  */
+  bb->flags &= ~BB_NEVER_REACHED;
+
   /* Note that since we are now transitioning from GIMPLE to RTL, we
      cannot use the gsi_*_bb() routines because they expect the basic
      block to be in GIMPLE, instead of RTL.  Therefore, we need to
Index: dominance.c
===================================================================
--- dominance.c (revision 194924)
+++ dominance.c (working copy)
@@ -524,7 +524,6 @@ calc_idoms (struct dom_info *di, bool reverse)
          if (bitmap_bit_p (di->fake_exit_edge, bb->index))
            {
              einext = ei;
-             einext.index = 0;
              goto do_fake_exit_edge;
            }
        }
@@ -543,6 +542,17 @@ calc_idoms (struct dom_info *di, bool reverse)
          einext = ei;
          ei_next (&einext);
 
+         if (reverse)
+           {
+             /* If this is a __builtin_unreachable block, skip it so that
+                predecessors don't get the EXIT block as post-dominator.  */
+             if (b->flags & BB_NEVER_REACHED)
+               {
+                 ei = einext;
+                 continue;
+               }
+           }
+
          if (b == en_block)
            {
            do_fake_exit_edge:
Index: basic-block.h
===================================================================
--- basic-block.h       (revision 194924)
+++ basic-block.h       (working copy)
@@ -790,6 +790,7 @@ extern int dfs_enumerate_from (basic_block, int,
                               basic_block *, int, const void *);
 extern void compute_dominance_frontiers (struct bitmap_head_def *);
 extern bitmap compute_idf (bitmap, struct bitmap_head_def *);
+extern void compute_control_dependences (struct bitmap_head_def *);
 
 /* In cfgrtl.c  */
 extern rtx block_label (basic_block);
Index: cfganal.c
===================================================================
--- cfganal.c   (revision 194924)
+++ cfganal.c   (working copy)
@@ -1079,34 +1079,46 @@ dfs_enumerate_from (basic_block bb, int reverse,
 
    The number of nodes touched by this algorithm is equal to the size
    of the dominance frontiers, no more, no less.
+
+   If FORWARD is false, run on the reverse CFG so that control dependences
+   are computed instead.
 */
 
 
 static void
-compute_dominance_frontiers_1 (bitmap_head *frontiers)
+compute_dominance_frontiers_1 (bitmap_head *frontiers, bool forward)
 {
   edge p;
   edge_iterator ei;
   basic_block b;
+  enum cdi_direction dir = forward ? CDI_DOMINATORS : CDI_POST_DOMINATORS;
+  basic_block entrybb = forward ? ENTRY_BLOCK_PTR : EXIT_BLOCK_PTR;
+
   FOR_EACH_BB (b)
     {
-      if (EDGE_COUNT (b->preds) >= 2)
+      vec<edge, va_gc> *preds = forward ? b->preds : b->succs;
+      if (EDGE_COUNT (preds) >= 2)
        {
-         FOR_EACH_EDGE (p, ei, b->preds)
+         FOR_EACH_EDGE (p, ei, preds)
            {
-             basic_block runner = p->src;
+             basic_block runner = forward ? p->src : p->dest;
              basic_block domsb;
-             if (runner == ENTRY_BLOCK_PTR)
+
+             if (runner == entrybb)
                continue;
+             if (!forward && (runner->flags & BB_NEVER_REACHED))
+               {
+                 bitmap_set_bit (&frontiers[runner->index], b->index);
+                 continue;
+               }
 
-             domsb = get_immediate_dominator (CDI_DOMINATORS, b);
+             domsb = get_immediate_dominator (dir, b);
              while (runner != domsb)
                {
                  if (!bitmap_set_bit (&frontiers[runner->index],
                                       b->index))
                    break;
-                 runner = get_immediate_dominator (CDI_DOMINATORS,
-                                                   runner);
+                 runner = get_immediate_dominator (dir, runner);
                }
            }
        }
@@ -1119,11 +1131,21 @@ compute_dominance_frontiers (bitmap_head *frontier
 {
   timevar_push (TV_DOM_FRONTIERS);
 
-  compute_dominance_frontiers_1 (frontiers);
+  compute_dominance_frontiers_1 (frontiers, /*forward=*/true);
 
   timevar_pop (TV_DOM_FRONTIERS);
 }
 
+void
+compute_control_dependences (bitmap_head *control_deps)
+{
+  timevar_push (TV_CONTROL_DEPENDENCES);
+
+  compute_dominance_frontiers_1 (control_deps, /*forward=*/false);
+
+  timevar_pop (TV_CONTROL_DEPENDENCES);
+}
+
 /* Given a set of blocks with variable definitions (DEF_BLOCKS),
    return a bitmap with all the blocks in the iterated dominance
    frontier of the blocks in DEF_BLOCKS.  DFS contains dominance

Reply via email to