Hi, Kyrylo

Just curious why the arm_only values has to be rejected?
Ideally the ge in reject_arm_only_values could also be a condition
move like ge=(c>7)? e : q - 4. LLVM is able to convert it to csel.
We just found this kind of diamond convert could help the PR126139
case, with allowing disjoint dest.

<[email protected]> 于2026年7月15日周三 17:09写道:
>
> From: Kyrylo Tkachov <[email protected]>
>
> This patch supersedes this one:
> https://gcc.gnu.org/pipermail/gcc-patches/2026-July/722391.html
> Consider that patch discarded.
>
> noce_convert_multiple_sets handles multi-set IF-THEN-JOIN regions but not
> IF-THEN-ELSE-JOIN diamonds.  After GIMPLE factors the selected load in
> Snappy's tag decoder, such a diamond still defines the next tag and input
> advance and leaves a data-dependent branch.
>
> Validate both arms using the existing multiple-set checks.  Choose a
> primary arm that defines every live-out pseudo defined by the secondary arm.
> Require the primary arm to define at least two distinct live-out pseudos.
> This leaves single-result diamonds to the existing specialized transforms.
> Collect the live-out destinations while validating each arm and compare the
> resulting bitmaps when choosing the primary arm.
>
> Evaluate secondary-arm SET_SRCs into fresh pseudos in dependency order
> and preserve their source locations.  For each primary destination, use the
> final secondary value as the other conditional-move input.  Use the incoming
> value when the secondary arm does not define that destination.
>
> Either CFG arm may be primary.  If the branch-target arm is primary,
> swap the arms in a local noce_if_info and reverse the select orientation.
> Reject speculative sequences that modify condition inputs.  If they clobber
> the condition code, stop sharing the comparison.  Rematerialize it for each
> conditional move instead.
>
> In outline, convert
>
>   secondary:
>     (set advance secondary_advance)
>   primary:
>     (set next primary_next)
>     (set advance primary_advance)
>
> to
>
>   (set secondary_tmp secondary_advance)
>   (set next
>        (if_then_else primary_p primary_next incoming_next))
>   (set advance
>        (if_then_else primary_p primary_advance secondary_tmp))
>
> where primary_p selects the primary arm.
>
> Weight arm costs by edge probability for speed and sum them for size.  The
> target noce_conversion_profitable_p hook retains the final profitability
> decision.
>
> Add execution and code-generation tests for symmetric and asymmetric
> diamonds, arm dependencies, repeated definitions, unmatched live-outs,
> condition-code clobbers, and both arm orientations.
>
> This removes the badly-predicted conditional branch in the Snappy decode
> loop and gives ~20% on the BM_UFlatMedley workload on my aarch64
> machine.
>
> On SPEC2026 it triggers a few thousand times with small improvements but
> nothing I would call out above noise levels.
>
> Bootstrapped and tested on aarch64-linux-gnu and x86_64-pc-linux-gnu.
>
> Ideally this would land after the fix for the wrong-code PR126184
> (https://gcc.gnu.org/pipermail/gcc-patches/2026-July/723467.html)
>
> Ok for trunk?
> Thanks,
> Kyrill
>
> gcc/ChangeLog:
>
>         PR tree-optimization/125557
>         * ifcvt.cc: Include "explow.h".
>         (noce_convert_multiple_sets): Handle diamond CFG cleanup.
>         (noce_convert_multiple_sets_1): Evaluate secondary-arm values in fresh
>         pseudos, preserve their source locations, and use their final values
>         as conditional-move inputs.  Use copy_to_mode_reg.  Reject secondary
>         sequences that modify condition inputs and handle condition-code
>         clobbers.
>         (bb_ok_for_noce_convert_multiple_sets): Add REQUIRE_MULTIPLE and
>         LIVE_OUT_DESTS.  Record distinct live-out destinations.
>         (noce_process_if_block): Recognize and cost multi-set diamonds.  
> Choose
>         the compatible primary arm from the validated live-out destinations.
>
> gcc/testsuite/ChangeLog:
>
>         PR tree-optimization/125557
>         * gcc.c-torture/execute/ifcvt-diamond-1.c: New test.
>         * gcc.target/aarch64/ifcvt_multiple_sets_diamond.c: New test.
>         * gcc.target/aarch64/ifcvt_multiple_sets_diamond_2.c: New test.
>         * gcc.target/aarch64/ifcvt_multiple_sets_diamond_3.c: New test.
>         * gcc.target/aarch64/ifcvt_multiple_sets_diamond_4.c: New test.
>         * gcc.target/aarch64/ifcvt_multiple_sets_diamond_5.c: New test.
>         * gcc.target/i386/ifcvt-multiple-sets-diamond-1.c: New test.
>
> Signed-off-by: Kyrylo Tkachov <[email protected]>
> ---
>  gcc/ifcvt.cc                                  | 216 +++++++++++++++---
>  .../gcc.c-torture/execute/ifcvt-diamond-1.c   | 143 ++++++++++++
>  .../aarch64/ifcvt_multiple_sets_diamond.c     |  66 ++++++
>  .../aarch64/ifcvt_multiple_sets_diamond_2.c   |  52 +++++
>  .../aarch64/ifcvt_multiple_sets_diamond_3.c   |  57 +++++
>  .../aarch64/ifcvt_multiple_sets_diamond_4.c   |  64 ++++++
>  .../aarch64/ifcvt_multiple_sets_diamond_5.c   |  31 +++
>  .../i386/ifcvt-multiple-sets-diamond-1.c      |  44 ++++
>  8 files changed, 644 insertions(+), 29 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.c-torture/execute/ifcvt-diamond-1.c
>  create mode 100644 
> gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_diamond.c
>  create mode 100644 
> gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_diamond_2.c
>  create mode 100644 
> gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_diamond_3.c
>  create mode 100644 
> gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_diamond_4.c
>  create mode 100644 
> gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_diamond_5.c
>  create mode 100644 
> gcc/testsuite/gcc.target/i386/ifcvt-multiple-sets-diamond-1.c
>
> diff --git a/gcc/ifcvt.cc b/gcc/ifcvt.cc
> index 03d9a4c6ab3..925e4e331b9 100644
> --- a/gcc/ifcvt.cc
> +++ b/gcc/ifcvt.cc
> @@ -37,6 +37,7 @@
>  #include "cfgrtl.h"
>  #include "cfganal.h"
>  #include "cfgcleanup.h"
> +#include "explow.h"
>  #include "expr.h"
>  #include "output.h"
>  #include "cfgloop.h"
> @@ -3771,13 +3772,17 @@ try_emit_cmove_seq (struct noce_if_info *if_info, rtx 
> temp,
>     conditional set to use the temporary we introduced earlier.
>
>     IF_INFO contains the useful information about the block structure and
> -   jump instructions.  */
> +   jump instructions.  For an IF-THEN-ELSE-JOIN, first evaluate the secondary
> +   arm's sets into temporaries and retain their final values for the
> +   conditional moves.  Return true if the replacement is valid and profitable
> +   and its CFG changes have been committed, otherwise return false.  */
>
>  static bool
>  noce_convert_multiple_sets (struct noce_if_info *if_info)
>  {
>    basic_block test_bb = if_info->test_bb;
>    basic_block then_bb = if_info->then_bb;
> +  basic_block else_bb = if_info->else_bb;
>    basic_block join_bb = if_info->join_bb;
>    rtx_insn *jump = if_info->jump;
>    rtx_insn *cond_earliest;
> @@ -3887,8 +3892,16 @@ noce_convert_multiple_sets (struct noce_if_info 
> *if_info)
>    emit_insn_before_setloc (seq, if_info->jump,
>                            INSN_LOCATION (insn_info.last 
> ()->unmodified_insn));
>
> -  /* Clean up THEN_BB and the edges in and out of it.  */
> -  remove_edge (find_edge (test_bb, join_bb));
> +  /* Clean up the THEN (and, for a diamond, ELSE) block and the edges into 
> and
> +     out of the if-region.  An IF-THEN-ELSE-JOIN has no test->join edge.
> +     Deleting ELSE_BB removes the test->else and else->join edges instead.  
> */
> +  if (else_bb)
> +    {
> +      delete_basic_block (else_bb);
> +      num_true_changes++;
> +    }
> +  else
> +    remove_edge (find_edge (test_bb, join_bb));
>    remove_edge (find_edge (then_bb, join_bb));
>    redirect_edge_and_branch_force (single_succ_edge (test_bb), join_bb);
>    delete_basic_block (then_bb);
> @@ -3906,8 +3919,15 @@ noce_convert_multiple_sets (struct noce_if_info 
> *if_info)
>    return true;
>  }
>
> -/* This goes through all relevant insns of IF_INFO->then_bb and tries to 
> create
> -   conditional moves.  Information for the insns is kept in INSN_INFO.  */
> +/* Try to emit the multiple-set conversion described by IF_INFO.  INSN_INFO
> +   holds the primary-arm metadata.  For a diamond, evaluate the secondary arm
> +   first and retain its final values for the conditional moves.
> +
> +   LAST_NEEDS_COMPARISON is -1 on the first attempt.  Record in it the last 
> set
> +   that needs a temporary to preserve the comparison, then use that boundary
> +   on the second attempt.  Set USE_COND_EARLIEST if the emitted sequence uses
> +   IF_INFO->cond_earliest.  Return true if the complete sequence was
> +   emitted.  */
>
>  static bool
>  noce_convert_multiple_sets_1 (struct noce_if_info *if_info,
> @@ -3933,6 +3953,73 @@ noce_convert_multiple_sets_1 (struct noce_if_info 
> *if_info,
>    int count = 0;
>    bool second_try = *last_needs_comparison != -1;
>    *use_cond_earliest = false;
> +  auto_delete_vec<noce_multiple_sets_info> else_insn_info;
> +
> +  /* For an IF-THEN-ELSE-JOIN, emit the else block's computations first into
> +     fresh temporaries.  This leaves the incoming register values available 
> to
> +     the then block.  The conditional moves below select the then values or
> +     these else values.  */
> +  if (if_info->else_bb)
> +    {
> +      init_noce_multiple_sets_info (if_info->else_bb, else_insn_info);
> +      int else_count = 0;
> +      rtx_insn *before_else = get_last_insn ();
> +      location_t saved_location = curr_insn_location ();
> +      rtx_insn *else_insn;
> +      FOR_BB_INSNS (if_info->else_bb, else_insn)
> +       {
> +         if (!active_insn_p (else_insn))
> +           continue;
> +
> +         noce_multiple_sets_info *info = else_insn_info[else_count];
> +         rtx set = single_set (else_insn);
> +         gcc_checking_assert (set && REG_P (SET_DEST (set))
> +                              && !HARD_REGISTER_P (SET_DEST (set)));
> +
> +         rtx target = SET_DEST (set);
> +         rtx value = copy_rtx (SET_SRC (set));
> +         int i, ii;
> +         FOR_EACH_VEC_ELT (info->rewired_src, i, ii)
> +           value = simplify_replace_rtx (value,
> +                                         else_insn_info[ii]->target,
> +                                         else_insn_info[ii]->temporary);
> +
> +         set_curr_insn_location (INSN_LOCATION (else_insn));
> +         rtx temporary = copy_to_mode_reg (GET_MODE (target), value);
> +
> +         info->target = target;
> +         info->temporary = temporary;
> +         info->unmodified_insn = else_insn;
> +         else_count++;
> +       }
> +
> +      set_curr_insn_location (saved_location);
> +
> +      gcc_checking_assert (else_count == (int) else_insn_info.length ());
> +
> +      /* These insns run ahead of the conditional moves.  If they change a
> +        register the comparison reads we cannot reuse it, so bail.  If they
> +        only clobber the condition code, drop the shared compare so that 
> every
> +        move re-materializes its own.  */
> +
> +      rtx_insn *first_else
> +       = before_else ? NEXT_INSN (before_else) : get_insns ();
> +      for (rtx_insn *ei = first_else; ei; ei = NEXT_INSN (ei))
> +       {
> +         if (modified_in_p (cond, ei))
> +           {
> +             end_sequence ();
> +             return false;
> +           }
> +         if (cc_cmp
> +             && (modified_in_p (cc_cmp, ei)
> +                 || (rev_cc_cmp && modified_in_p (rev_cc_cmp, ei))))
> +           {
> +             cc_cmp = NULL_RTX;
> +             rev_cc_cmp = NULL_RTX;
> +           }
> +       }
> +    }
>
>    FOR_BB_INSNS (then_bb, insn)
>      {
> @@ -3957,6 +4044,17 @@ noce_convert_multiple_sets_1 (struct noce_if_info 
> *if_info,
>
>        rtx old_val = target;
>
> +      /* Use the final value assigned to TARGET on the else arm.  Scanning in
> +        reverse is important when the arm assigns the same register more than
> +        once.  */
> +      if (if_info->else_bb)
> +       for (int j = else_insn_info.length () - 1; j >= 0; --j)
> +         if (rtx_equal_p (target, else_insn_info[j]->target))
> +           {
> +             old_val = else_insn_info[j]->temporary;
> +             break;
> +           }
> +
>        /* As we are transforming
>          if (x > y)
>            {
> @@ -4244,19 +4342,29 @@ init_noce_multiple_sets_info (basic_block bb,
>  }
>
>  /* Return true iff basic block TEST_BB is suitable for conversion to a
> -   series of conditional moves.  Also check that we have more than one
> -   set (other routines can handle a single set better than we would),
> -   and fewer than PARAM_MAX_RTL_IF_CONVERSION_INSNS sets.  While going
> -   through the insns store the sum of their potential costs in COST.  */
> +   series of conditional moves.  Unless REQUIRE_MULTIPLE is false, also check
> +   that we have more than one set (other routines can handle a single set
> +   better than we would).  A diamond arm may have a single set when it is
> +   selected as the secondary arm.  Require fewer than
> +   PARAM_MAX_RTL_IF_CONVERSION_INSNS sets.  While going through the insns 
> store
> +   the sum of their potential costs in COST.  On success, if LIVE_OUT_DESTS 
> is
> +   nonnull, record the distinct pseudo destinations that are live out of
> +   TEST_BB.  */
>
>  static bool
> -bb_ok_for_noce_convert_multiple_sets (basic_block test_bb, unsigned *cost)
> +bb_ok_for_noce_convert_multiple_sets (basic_block test_bb, unsigned *cost,
> +                                     bool require_multiple = true,
> +                                     bitmap live_out_dests = NULL)
>  {
>    rtx_insn *insn;
>    unsigned count = 0;
>    unsigned param = param_max_rtl_if_conversion_insns;
>    bool speed_p = optimize_bb_for_speed_p (test_bb);
>    unsigned potential_cost = 0;
> +  if (live_out_dests)
> +    bitmap_clear (live_out_dests);
> +  bitmap bb_live_out
> +    = live_out_dests ? df_get_live_out (test_bb) : NULL;
>
>    FOR_BB_INSNS (test_bb, insn)
>      {
> @@ -4289,6 +4397,9 @@ bb_ok_for_noce_convert_multiple_sets (basic_block 
> test_bb, unsigned *cost)
>        if (!can_conditionally_move_p (GET_MODE (dest)))
>         return false;
>
> +      if (live_out_dests && bitmap_bit_p (bb_live_out, REGNO (dest)))
> +       bitmap_set_bit (live_out_dests, REGNO (dest));
> +
>        potential_cost += insn_cost (insn, speed_p);
>
>        count++;
> @@ -4297,11 +4408,13 @@ bb_ok_for_noce_convert_multiple_sets (basic_block 
> test_bb, unsigned *cost)
>    *cost += potential_cost;
>
>    /* If we would only put out one conditional move, the other strategies
> -     this pass tries are better optimized and will be more appropriate.
> +     this pass tries are better optimized and will be more appropriate, so
> +     require more than one set unless REQUIRE_MULTIPLE is false.  A diamond
> +     arm may have one set when it is selected as the secondary arm.
>       Some targets want to strictly limit the number of conditional moves
>       that are emitted, they set this through PARAM, we need to respect
>       that.  */
> -  return count > 1 && count <= param;
> +  return count >= (require_multiple ? 2u : 1u) && count <= param;
>  }
>
>  /* Compute average of two given costs weighted by relative probabilities
> @@ -4342,10 +4455,11 @@ noce_process_if_block (struct noce_if_info *if_info)
>       (2) x = b; if (...) x = a;
>       (3) if (...) x = a;   // as if with an initial x = x.
>       (4) if (...) { x = a; y = b; z = c; }  // Like 3, for multiple SETS.
> +     (5) A multi-set IF-THEN-ELSE-JOIN.
>       The later patterns require jumps to be more expensive.
> -     For the if (...) x = a; else x = b; case we allow multiple insns
> -     inside the then and else blocks as long as their only effect is
> -     to calculate a value for x.
> +     For the diamond case, use an arm that sets at least two distinct 
> live-out
> +     pseudos as the primary arm.  Every live-out destination set by the
> +     secondary arm must also be set by the primary arm.
>       ??? For future expansion, further expand the "multiple X" rules.  */
>
>    /* First look for multiple SETS.
> @@ -4354,25 +4468,69 @@ noce_process_if_block (struct noce_if_info *if_info)
>       If a target re-uses the existing CC comparison we keep track of that
>       and add the costs before default noce_conversion_profitable_p.  */
>
> -  unsigned potential_cost = if_info->original_cost;
>    unsigned old_cost = if_info->original_cost;
> -  if (!else_bb
> -      && HAVE_conditional_move
> -      && bb_ok_for_noce_convert_multiple_sets (then_bb, &potential_cost))
> -    {
> -      /* Temporarily set the original costs to what we estimated so
> -        we can determine if the transformation is worth it.  */
> -      if_info->original_cost = potential_cost;
> -      if (noce_convert_multiple_sets (if_info))
> +  unsigned ms_then_cost = 0, ms_else_cost = 0;
> +  auto_bitmap ms_then_live_out_dests, ms_else_live_out_dests;
> +  noce_if_info ms_if_info = *if_info;
> +  bool multiple_sets_p = false;
> +
> +  if (HAVE_conditional_move)
> +    {
> +      if (!else_bb)
> +       multiple_sets_p
> +         = bb_ok_for_noce_convert_multiple_sets (then_bb, &ms_then_cost);
> +      else if (!if_info->then_else_reversed
> +              && bb_ok_for_noce_convert_multiple_sets (then_bb,
> +                                                      &ms_then_cost, false,
> +                                                      ms_then_live_out_dests)
> +              && bb_ok_for_noce_convert_multiple_sets (else_bb,
> +                                                      &ms_else_cost, false,
> +                                                      
> ms_else_live_out_dests))
>         {
> -         if (dump_file && if_info->transform_name)
> +         if (bitmap_count_bits (ms_then_live_out_dests) >= 2
> +             && !bitmap_intersect_compl_p (ms_else_live_out_dests,
> +                                            ms_then_live_out_dests))
> +           multiple_sets_p = true;
> +         else if (bitmap_count_bits (ms_else_live_out_dests) >= 2
> +                  && !bitmap_intersect_compl_p (ms_then_live_out_dests,
> +                                                   ms_else_live_out_dests))
> +           {
> +             /* The branch-target arm is the compatible multi-set superset.
> +                Make it the primary arm and reverse the select orientation.  
> */
> +             std::swap (ms_if_info.then_bb, ms_if_info.else_bb);
> +             ms_if_info.then_else_reversed
> +               = !ms_if_info.then_else_reversed;
> +             std::swap (ms_then_cost, ms_else_cost);
> +             multiple_sets_p = true;
> +           }
> +       }
> +    }
> +
> +  if (multiple_sets_p)
> +    {
> +      /* The original code runs the comparison and one arm.  Estimate that 
> cost
> +        (for a diamond weight the two arms by their probabilities) and let
> +        noce_convert_multiple_sets convert only if the conditional moves come
> +        out cheaper.  */
> +      unsigned potential_cost = old_cost + ms_then_cost;
> +      if (ms_if_info.else_bb)
> +       {
> +         if (optimize_bb_for_speed_p (test_bb))
> +           potential_cost
> +             = old_cost + average_cost (ms_then_cost, ms_else_cost,
> +                                        find_edge (test_bb,
> +                                                   ms_if_info.then_bb));
> +         else
> +           potential_cost = old_cost + ms_then_cost + ms_else_cost;
> +       }
> +      ms_if_info.original_cost = potential_cost;
> +      if (noce_convert_multiple_sets (&ms_if_info))
> +       {
> +         if (dump_file && ms_if_info.transform_name)
>             fprintf (dump_file, "if-conversion succeeded through %s\n",
> -                    if_info->transform_name);
> +                    ms_if_info.transform_name);
>           return true;
>         }
> -
> -      /* Restore the original costs.  */
> -      if_info->original_cost = old_cost;
>      }
>
>    bool speed_p = optimize_bb_for_speed_p (test_bb);
> diff --git a/gcc/testsuite/gcc.c-torture/execute/ifcvt-diamond-1.c 
> b/gcc/testsuite/gcc.c-torture/execute/ifcvt-diamond-1.c
> new file mode 100644
> index 00000000000..b6a883e3de4
> --- /dev/null
> +++ b/gcc/testsuite/gcc.c-torture/execute/ifcvt-diamond-1.c
> @@ -0,0 +1,143 @@
> +/* Runtime correctness of if-converted IF-THEN-ELSE-JOIN diamonds with
> +   multiple output registers (noce_convert_multiple_sets).  */
> +
> +long g1, g2, g3;
> +
> +__attribute__ ((noipa)) void
> +diamond2 (long c, long x, long y)
> +{
> +  long a, b;
> +  if (c & 3)
> +    {
> +      a = x + 1;
> +      b = y - 2;
> +    }
> +  else
> +    {
> +      a = x * 4;
> +      b = y + 9;
> +    }
> +  g1 = a;
> +  g2 = b;
> +}
> +
> +/* Then arm reads an earlier then output (arm-internal dependency).  */
> +__attribute__ ((noipa)) void
> +diamond3 (long c, long p, long q)
> +{
> +  long a, b, d;
> +  if (c > 0)
> +    {
> +      a = p ^ q;
> +      b = a + 7;
> +      d = q * 2;
> +    }
> +  else
> +    {
> +      a = p & q;
> +      b = q | 1;
> +      d = p - 3;
> +    }
> +  g1 = a;
> +  g2 = b;
> +  g3 = d;
> +}
> +
> +/* A single output in the else arm.  The other register keeps its incoming
> +   value on the else path.  */
> +__attribute__ ((noipa)) void
> +diamond_then2_else1 (long c, long x, long y)
> +{
> +  long a = x, b = y;
> +  if (c < 0)
> +    {
> +      a = x + 100;
> +      b = y + 200;
> +    }
> +  else
> +    a = x - 50;
> +  g1 = a;
> +  g2 = b;
> +}
> +
> +/* Keep the single-set arm as the likely fallthrough block.  The multi-set
> +   arm must become the primary arm of the conversion.  */
> +__attribute__ ((noipa)) void
> +diamond_reversed_then2_else1 (long c, long x)
> +{
> +  long type = c & 3;
> +  long next = type;
> +  long advance;
> +  if (__builtin_expect (type != 0, 1))
> +    advance = type + 1;
> +  else
> +    {
> +      next = x + 1;
> +      advance = x + 2;
> +    }
> +  g1 = next;
> +  g2 = advance;
> +}
> +
> +/* Each arm produces a live-out value that the other arm does not.  */
> +__attribute__ ((noipa)) void
> +diamond_unmatched_liveouts (long c, long p, long q)
> +{
> +  long a = 100, t = 300, e = 200;
> +  if (c & 4)
> +    {
> +      a = p + q;
> +      t = p * 2;
> +    }
> +  else
> +    {
> +      a = p - q;
> +      e = q * 2;
> +    }
> +  g1 = a;
> +  g2 = e;
> +  g3 = t;
> +}
> +
> +int
> +main (void)
> +{
> +  for (long c = -4; c <= 12; c++)
> +    for (long p = -6; p <= 6; p++)
> +      for (long q = -6; q <= 6; q++)
> +       {
> +         diamond2 (c, p, q);
> +         if (g1 != ((c & 3) ? p + 1 : p * 4)
> +             || g2 != ((c & 3) ? q - 2 : q + 9))
> +           __builtin_abort ();
> +
> +         diamond3 (c, p, q);
> +         {
> +           long ea = (c > 0) ? (p ^ q) : (p & q);
> +           long eb = (c > 0) ? ea + 7 : (q | 1);
> +           long ed = (c > 0) ? (q * 2) : (p - 3);
> +           if (g1 != ea || g2 != eb || g3 != ed)
> +             __builtin_abort ();
> +         }
> +
> +         diamond_then2_else1 (c, p, q);
> +         if (g1 != ((c < 0) ? p + 100 : p - 50)
> +             || g2 != ((c < 0) ? q + 200 : q))
> +           __builtin_abort ();
> +
> +         diamond_reversed_then2_else1 (c, p);
> +         {
> +           long type = c & 3;
> +           if (g1 != (type ? type : p + 1)
> +               || g2 != (type ? type + 1 : p + 2))
> +             __builtin_abort ();
> +         }
> +
> +         diamond_unmatched_liveouts (c, p, q);
> +         if (g1 != ((c & 4) ? p + q : p - q)
> +             || g2 != ((c & 4) ? 200 : q * 2)
> +             || g3 != ((c & 4) ? p * 2 : 300))
> +           __builtin_abort ();
> +       }
> +  return 0;
> +}
> diff --git a/gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_diamond.c 
> b/gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_diamond.c
> new file mode 100644
> index 00000000000..58105001fd2
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_diamond.c
> @@ -0,0 +1,66 @@
> +/* Test if-conversion of IF-THEN-ELSE-JOIN diamonds with multiple output
> +   registers through noce_convert_multiple_sets.  */
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-rtl-ce1" } */
> +
> +void sink2 (long, long);
> +
> +/* Two outputs, both arms write the same registers.  */
> +void
> +diamond_arith (long c, long x, long y)
> +{
> +  long a, b;
> +  if (c > 7)
> +    {
> +      a = x + 1;
> +      b = y - 2;
> +    }
> +  else
> +    {
> +      a = x * 4;
> +      b = y + 9;
> +    }
> +  sink2 (a, b);
> +}
> +
> +/* Two outputs computed from constants on each arm.  */
> +void
> +diamond_const (long c, long x, long y)
> +{
> +  long a, b;
> +  if (c == 3)
> +    {
> +      a = 5;
> +      b = 7;
> +    }
> +  else
> +    {
> +      a = 9;
> +      b = 11;
> +    }
> +  sink2 (a, b);
> +}
> +
> +/* Two outputs in the then arm, a single output in the else arm.  The second
> +   register keeps its incoming value on the else path.  */
> +void
> +diamond_then2_else1 (long c, long x, long y)
> +{
> +  long a = x, b = y;
> +  if (c < 0)
> +    {
> +      a = x + 100;
> +      b = y + 200;
> +    }
> +  else
> +    a = x - 50;
> +  sink2 (a, b);
> +}
> +
> +/* { dg-final { scan-rtl-dump-times "if-conversion succeeded through 
> noce_convert_multiple_sets" 3 "ce1" } } */
> +
> +/* The converted diamonds are branchless: no conditional branch remains.  */
> +/* { dg-final { scan-assembler-not 
> {\tb(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le)\t} } } */
> +/* { dg-final { scan-assembler-not "\tcbn?z\t" } } */
> +/* { dg-final { scan-assembler-not "\ttbn?z\t" } } */
> +/* { dg-final { scan-assembler "\tcsel\t" } } */
> diff --git a/gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_diamond_2.c 
> b/gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_diamond_2.c
> new file mode 100644
> index 00000000000..f5891730cad
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_diamond_2.c
> @@ -0,0 +1,52 @@
> +/* Each arm of the diamond loads from a selected address and advances a
> +   pointer by a selected amount.  Once the two arm loads are commoned the
> +   diamond writes two registers on both arms (the load result and the
> +   advance), which noce_convert_multiple_sets turns into conditional moves.  
> */
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-rtl-ce1" } */
> +
> +#include <stddef.h>
> +#include <stdint.h>
> +
> +extern const int16_t lentab[256];
> +
> +static inline uint32_t
> +extract (uint32_t val, size_t type)
> +{
> +  const uint64_t masks = 0x0000FFFF00FF0000ull;
> +  return val & (uint32_t) ((masks >> (type * 16)) & 0xFFFF);
> +}
> +
> +ptrdiff_t
> +f (const uint8_t *ip, size_t tag, const uint8_t *end, ptrdiff_t op)
> +{
> +  do
> +    {
> +      const uint8_t *old_ip = ip;
> +      ptrdiff_t lmo = lentab[tag];
> +      size_t type = tag & 3;
> +      if (type == 0)
> +       {
> +         size_t n = (tag >> 2) + 1;
> +         tag = ip[n];
> +         ip += n + 1;
> +       }
> +      else
> +       {
> +         tag = ip[type];
> +         ip += type + 1;
> +       }
> +      uint32_t next = (uint32_t) old_ip[0] | ((uint32_t) old_ip[1] << 8);
> +      ptrdiff_t extracted = extract (next, type);
> +      op += lmo - extracted;
> +    }
> +  while (ip < end);
> +  return op;
> +}
> +
> +/* { dg-final { scan-rtl-dump "if-conversion succeeded through 
> noce_convert_multiple_sets" "ce1" } } */
> +/* { dg-final { scan-assembler-times "\tcsinc\t" 2 } } */
> +/* { dg-final { scan-assembler-times "\tldrb\t" 1 } } */
> +/* { dg-final { scan-assembler-not {\tb(eq|ne)\t} } } */
> +/* { dg-final { scan-assembler-not {\tcbn?z\t} } } */
> +/* { dg-final { scan-assembler-not {\ttbn?z\t} } } */
> diff --git a/gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_diamond_3.c 
> b/gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_diamond_3.c
> new file mode 100644
> index 00000000000..c459703e9bd
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_diamond_3.c
> @@ -0,0 +1,57 @@
> +/* Test dependencies between sets in both arms of an IF-THEN-ELSE-JOIN
> +   diamond.  */
> +/* { dg-do run } */
> +/* { dg-options "-O2 --param=max-rtl-if-conversion-unpredictable-cost=100 
> -fdump-rtl-ce1" } */
> +/* Keep both assignments to x in the same RTL pseudo.  */
> +/* { dg-additional-options "-fno-tree-ter -fno-tree-coalesce-vars" } */
> +
> +volatile long gx, gy;
> +
> +__attribute__ ((noipa)) void
> +diamond_dependencies (long c, long a, long b)
> +{
> +  long x, y;
> +  if (c & 1)
> +    {
> +      x = a + 1;
> +      y = x ^ b;
> +      x = y + 3;
> +    }
> +  else
> +    {
> +      x = b - 1;
> +      y = x ^ a;
> +      x = y - 3;
> +    }
> +  gx = x;
> +  gy = y;
> +}
> +
> +__attribute__ ((optimize ("O0"))) int
> +main (void)
> +{
> +  for (long c = -3; c <= 3; ++c)
> +    for (long a = -5; a <= 5; ++a)
> +      for (long b = -5; b <= 5; ++b)
> +       {
> +         long x, y;
> +         diamond_dependencies (c, a, b);
> +         if (c & 1)
> +           {
> +             long first_x = a + 1;
> +             y = first_x ^ b;
> +             x = y + 3;
> +           }
> +         else
> +           {
> +             long first_x = b - 1;
> +             y = first_x ^ a;
> +             x = y - 3;
> +           }
> +         if (gx != x || gy != y)
> +           __builtin_abort ();
> +       }
> +  return 0;
> +}
> +
> +/* { dg-final { scan-rtl-dump-times "if-conversion succeeded through 
> noce_convert_multiple_sets" 1 "ce1" } } */
> diff --git a/gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_diamond_4.c 
> b/gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_diamond_4.c
> new file mode 100644
> index 00000000000..22617c4d098
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_diamond_4.c
> @@ -0,0 +1,64 @@
> +/* { dg-do run } */
> +/* { dg-options "-O2 --param=max-rtl-if-conversion-unpredictable-cost=100 
> -fdump-rtl-ce1" } */
> +
> +volatile long ga, gt, ge;
> +
> +__attribute__ ((noipa)) void
> +convertible (long c, long p, long q)
> +{
> +  long a, t;
> +  if (c > 7)
> +    {
> +      a = p + 1;
> +      t = q + 2;
> +    }
> +  else
> +    {
> +      a = p - 3;
> +      t = q - 4;
> +    }
> +  ga = a;
> +  gt = t;
> +}
> +
> +__attribute__ ((noipa)) void
> +reject_arm_only_values (long c, long p, long q, long t, long e)
> +{
> +  long a;
> +  if (c > 7)
> +    {
> +      a = p + 1;
> +      t = q + 2;
> +    }
> +  else
> +    {
> +      a = p - 3;
> +      e = q - 4;
> +    }
> +  ga = a;
> +  gt = t;
> +  ge = e;
> +}
> +
> +__attribute__ ((optimize ("O0"))) int
> +main (void)
> +{
> +  convertible (8, 10, 20);
> +  if (ga != 11 || gt != 22)
> +    __builtin_abort ();
> +  convertible (7, 10, 20);
> +  if (ga != 7 || gt != 16)
> +    __builtin_abort ();
> +
> +  reject_arm_only_values (8, 10, 20, 31, 47);
> +  if (ga != 11 || gt != 22 || ge != 47)
> +    __builtin_abort ();
> +  reject_arm_only_values (7, 10, 20, 31, 47);
> +  if (ga != 7 || gt != 31 || ge != 16)
> +    __builtin_abort ();
> +  return 0;
> +}
> +
> +/* The first diamond converts.  The second must be rejected because each arm
> +   has a live-out value not assigned by the other arm.  */
> +/* { dg-final { scan-rtl-dump-times "if-conversion succeeded through 
> noce_convert_multiple_sets" 1 "ce1" } } */
> diff --git a/gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_diamond_5.c 
> b/gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_diamond_5.c
> new file mode 100644
> index 00000000000..7490df7ddc0
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_diamond_5.c
> @@ -0,0 +1,31 @@
> +/* Test a diamond whose likely fallthrough arm has one set and whose other
> +   arm has multiple sets, including a live-out not changed by the fallthrough
> +   arm.  */
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -fdump-rtl-ce1" } */
> +/* { dg-additional-options 
> "--param=max-rtl-if-conversion-predictable-cost=100" } */
> +
> +unsigned long
> +f (const unsigned char *p, unsigned long tag)
> +{
> +  unsigned long type = tag & 3;
> +  unsigned long next = type;
> +  unsigned long advance;
> +  if (__builtin_expect (type != 0, 1))
> +    advance = type + 1;
> +  else
> +    {
> +      unsigned long base = tag >> 2;
> +      next = base + 1;
> +      advance = base + 2;
> +    }
> +  return p[next] + (advance << 8);
> +}
> +
> +/* { dg-final { scan-rtl-dump-times "if-conversion succeeded through 
> noce_convert_multiple_sets" 1 "ce1" } } */
> +/* { dg-final { scan-assembler-times "\tcsinc\t" 1 } } */
> +/* { dg-final { scan-assembler-times "\tcsel\t" 1 } } */
> +/* { dg-final { scan-assembler-times "\tldrb\t" 1 } } */
> +/* { dg-final { scan-assembler-not {\tb(eq|ne)\t} } } */
> +/* { dg-final { scan-assembler-not {\tcbn?z\t} } } */
> +/* { dg-final { scan-assembler-not {\ttbn?z\t} } } */
> diff --git a/gcc/testsuite/gcc.target/i386/ifcvt-multiple-sets-diamond-1.c 
> b/gcc/testsuite/gcc.target/i386/ifcvt-multiple-sets-diamond-1.c
> new file mode 100644
> index 00000000000..fb4491074f3
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/ifcvt-multiple-sets-diamond-1.c
> @@ -0,0 +1,44 @@
> +/* { dg-do run } */
> +/* { dg-require-effective-target lp64 } */
> +/* { dg-options "-O2 -mtune=generic -fdump-rtl-ce1" } */
> +/* { dg-additional-options 
> "--param=max-rtl-if-conversion-predictable-cost=100" } */
> +
> +/* The single-set arm is the likely fallthrough block.  Speculative 
> arithmetic
> +   clobbers FLAGS, so each conditional move must re-materialize the
> +   comparison.  */
> +
> +volatile long ga, gb;
> +
> +__attribute__ ((noipa)) void
> +f (long c, long x, long y, long b)
> +{
> +  long a;
> +  if (__builtin_expect (c <= 7, 1))
> +    a = y + 3;
> +  else
> +    {
> +      a = x + 1;
> +      b = y + 2;
> +    }
> +  ga = a;
> +  gb = b;
> +}
> +
> +/* Keep the runtime driver out of noce so that the dump count is specific to
> +   F.  */
> +__attribute__ ((optimize ("O0"))) int
> +main (void)
> +{
> +  for (long c = 5; c != 11; ++c)
> +    for (long x = -8; x != 9; ++x)
> +      for (long y = -8; y != 9; ++y)
> +       {
> +         f (c, x, y, 4);
> +         if (ga != (c > 7 ? x + 1 : y + 3)
> +             || gb != (c > 7 ? y + 2 : 4))
> +           __builtin_abort ();
> +       }
> +  return 0;
> +}
> +
> +/* { dg-final { scan-rtl-dump-times "if-conversion succeeded through 
> noce_convert_multiple_sets" 1 "ce1" } } */
> --
> 2.50.1 (Apple Git-155)
>

Reply via email to