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)