Hi Hongyu, > On 16 Jul 2026, at 10:18, Hongyu Wang <[email protected]> wrote: > > 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.
There’s no reason not to allow it. I was just trying to keep the scope a bit more targeted to PR125557. I can handle this in a separate follow-up patch though. Thanks, Kyrill > > <[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) >>
