On Tue, Aug 25, 2026 at 7:48 PM Aldy Hernandez <[email protected]> wrote:
>
> The backward threader can resolve a computed goto's target along a
> path, but the thread is dropped at copy time when it is the target of
> another abnormal edge:
>
>   Registering jump thread: (2, 4) incoming edge;  (4, 5) nocopy;
>   Cannot copy the blocks in the path:   Cancelling jump thread:
>
> The problem is that can_copy_bbs_p refuses any region with an
> intra-region abnormal edge by design because it would end up
> redirecting abnormal edges which is a no-no.
>
> Work around this by copying the last block by hand if we know we'll
> end up stripping the abnormal flag when we resolve the path.  This
> allows us to thread paths that end in an abnormal edge, though
> abnormal edges mid-path are still disallowed.  Though that's not a
> regression because AFAICT, DOM won't do that either.
>
> BTW, I explored various ways of attacking this, and ultimately landed
> on the cheapest way to handle this by touching the least amount of
> things, cause GCC parts tend to be sticky-- and I don't want to be
> left holding the bag ;-).
>
> Pardon the copious comments and diagrams; I had a hard time wrapping
> my head around this.
>
> OK?

Huh, so what's the situation?  The last block has a control statement we
can statically evaluate but also an abnormal outgoing edge?  And the
result is a copy of the block but only with the static determined edge?

>
>         PR tree-optimization/126906
>
> gcc/ChangeLog:
>
>         * tree-ssa-threadupdate.cc
>         (back_jt_path_registry::duplicate_thread_path): Copy the last
>         block by hand, keeping its outgoing edges on the original
>         destinations; check the two copy phases separately.  Assert
>         that the last-block redirect fixup no longer triggers.
>
> gcc/testsuite/ChangeLog:
>
>         * gcc.dg/tree-ssa/ssa-thread-25.c: New test.
> ---
>  gcc/testsuite/gcc.dg/tree-ssa/ssa-thread-25.c | 30 ++++++++
>  gcc/tree-ssa-threadupdate.cc                  | 77 +++++++++++++++----
>  2 files changed, 94 insertions(+), 13 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/ssa-thread-25.c
>
> diff --git a/gcc/testsuite/gcc.dg/tree-ssa/ssa-thread-25.c 
> b/gcc/testsuite/gcc.dg/tree-ssa/ssa-thread-25.c
> new file mode 100644
> index 00000000000..526e96bd2c8
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/tree-ssa/ssa-thread-25.c
> @@ -0,0 +1,30 @@
> +/* PR tree-optimization/126906 */
> +/* { dg-do compile } */
> +/* { dg-options "-O2 --param=dom-jump-threading=0 
> -fdump-tree-ethread-details" } */
> +
> +/* The computed goto lives in the same block as the address-taken
> +   label lab2, so the goto's abnormal edge to lab2 points back into
> +   the block itself.  Threading the resolved paths must copy that
> +   block, which the generic copier used to refuse.  */
> +
> +void *sink;
> +
> +void
> +foo (int b)
> +{
> +  void *x;
> +  sink = &&lab2;
> +  if (b)
> +    x = &&lab;
> +  else
> +    x = &&done;
> +lab2:
> +  goto *x;
> +lab:
> +  sink = 0;
> +done:
> +  return;
> +}
> +
> +/* { dg-final { scan-tree-dump-times "Registering jump thread" 2 "ethread" } 
> } */
> +/* { dg-final { scan-tree-dump-not "Cancelling" "ethread" } } */
> diff --git a/gcc/tree-ssa-threadupdate.cc b/gcc/tree-ssa-threadupdate.cc
> index 54d2c419423..106d906548c 100644
> --- a/gcc/tree-ssa-threadupdate.cc
> +++ b/gcc/tree-ssa-threadupdate.cc
> @@ -2395,11 +2395,22 @@ back_jt_path_registry::duplicate_thread_path (edge 
> entry,
>  {
>    unsigned i;
>    class loop *loop = entry->dest->loop_father;
> -  edge exit_copy;
>    edge redirected;
>    profile_count curr_count;
>
> -  if (!can_copy_bbs_p (region, n_region))
> +  gcc_checking_assert (n_region > 0);
> +  basic_block last_bb = region[n_region - 1];
> +
> +  /* We're going to duplicate the last block by hand to avoid 
> can_copy_bbs_p's
> +     restriction of refusing to copy abnormal successor edges, so look at the
> +     last block in isolation...  */
> +  if (!can_duplicate_block_p (last_bb))
> +    {
> +      *failure_reason = "Cannot duplicate the last block in the path";
> +      return false;
> +    }
> +  /* ...then verify the rest of the path is copyable.  */
> +  if (!can_copy_bbs_p (region, n_region - 1))
>      {
>        *failure_reason = "Cannot copy the blocks in the path";
>        return false;
> @@ -2431,9 +2442,52 @@ back_jt_path_registry::duplicate_thread_path (edge 
> entry,
>
>    set_loop_copy (loop, loop);
>
> +  /* Copy all but the last block with copy_bbs, then copy the last block (B3)
> +     by hand.  For example, take the path B1 -> B2 -> B3 below, where B3 ends
> +     in a computed goto that the thread resolved to the exit edge B3 -ab-> 
> lab,
> +     and where one of the goto's other abnormal edges points back into the
> +     region:
> +
> +       B1 -> B2 -> B3 -ab-> lab
> +              ^    |
> +              +-ab-+
> +
> +     copy_bbs redirects every copied edge that targets a copied block
> +     into the copy.  With the above path, it would have to produce:
> +
> +       B1' -> B2' -> B3' -ab-> lab
> +               ^     |
> +               +-ab--+
> +
> +     redirecting the copied abnormal edge to B2'.  That cannot be done, 
> because
> +     the computed goto target is unknown at compile time.  This is why
> +     can_copy_bbs_p refuses such regions.
> +
> +     But since we know the only possible path out of B1'->B2'->B3' is "lab", 
> we
> +     can clear the abnormal flag, and ultimately remove the intra-path 
> abnormal
> +     edge since we know it won't happen.
> +
> +     We will end up with:
> +
> +       B1' -> B2' -> B3' -> lab
> +  */
>    basic_block *region_copy = XNEWVEC (basic_block, n_region);
> -  copy_bbs (region, n_region, region_copy, &exit, 1, &exit_copy, loop,
> -           split_edge_bb_loc (entry), false);
> +  basic_block after = split_edge_bb_loc (entry);
> +  if (n_region > 1)
> +    {
> +      copy_bbs (region, n_region - 1, region_copy, NULL, 0, NULL, loop,
> +               after, false);
> +      after = region_copy[n_region - 2];
> +    }
> +  basic_block last_bb_copy = duplicate_block (last_bb, NULL, after);
> +  region_copy[n_region - 1] = last_bb_copy;
> +  if (n_region > 1)
> +    {
> +      /* The edge into the original last block now enters its copy.  */
> +      edge sp = find_edge (after, last_bb);
> +      gcc_assert (sp && !(sp->flags & EDGE_ABNORMAL));
> +      redirect_edge_and_branch_force (sp, last_bb_copy);
> +    }
>
>    /* Fix up: copy_bbs redirects all edges pointing to copied blocks.  The
>       following code ensures that all the edges exiting the jump-thread path 
> are
> @@ -2482,17 +2536,14 @@ back_jt_path_registry::duplicate_thread_path (edge 
> entry,
>           continue;
>         }
>
> -      /* Special case the last block on the path: make sure that it does not
> -        jump back on the copied path, including back to itself.  */
> +      /* There's nothing to redirect here, since the last block on the path 
> was
> +        copied by hand and its edges point at the original destinations.  A
> +        follow-up patch will remove this assert.  */
>        if (i + 1 == n_region)
>         {
> -         FOR_EACH_EDGE (e, ei, bb->succs)
> -           if (bb_in_bbs (e->dest, region_copy, n_region))
> -             {
> -               basic_block orig = get_bb_original (e->dest);
> -               if (orig)
> -                 redirect_edge_and_branch_force (e, orig);
> -             }
> +         if (flag_checking)
> +           FOR_EACH_EDGE (e, ei, bb->succs)
> +             gcc_assert (!bb_in_bbs (e->dest, region_copy, n_region));
>           continue;
>         }
>
> --
> 2.47.3
>

Reply via email to