Ping.
Thanks,
Kyrill
> On 24 Jul 2026, at 14:28, Kyrylo Tkachov <[email protected]> wrote:
>
> Ping.
> Thanks,
> Kyrill
>
>> On 15 Jul 2026, at 11:06, Kyrylo Tkachov <[email protected]> wrote:
>>
>> 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)
>>
>