A swap-style idiom like tmp = a a = b b = tmp would be transformed like tmp_tmp = cond ? a : tmp tmp_a = cond ? b : a tmp_b = cond ? tmp_tmp : b [...] including rewiring the first source operand to previous writes (e.g. tmp -> tmp_tmp).
The code would recognize this, though, and change the last line to tmp_b = cond ? a : b. Without additional temporaries we can now emit the following sequence: tmp = a // (no condition here) a = cond ? b : a b = cond ? tmp : b avoiding any rewiring which would break things now. check_need_cmovs () finds swap-style idioms and marks the first of the three instructions as not needing a cmove. --- gcc/ifcvt.c | 97 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 78 insertions(+), 19 deletions(-) diff --git a/gcc/ifcvt.c b/gcc/ifcvt.c index 955f9541f60..09bf443656c 100644 --- a/gcc/ifcvt.c +++ b/gcc/ifcvt.c @@ -103,6 +103,7 @@ static rtx_insn *block_has_only_trap (basic_block); static void check_need_temps (basic_block bb, hash_map<rtx, bool> *need_temp, rtx cond); +static void check_need_cmovs (basic_block bb, hash_map<rtx, bool> *need_cmov); /* Count the number of non-jump active insns in BB. */ @@ -3207,6 +3208,7 @@ noce_convert_multiple_sets (struct noce_if_info *if_info) int count = 0; hash_map<rtx, bool> need_temps; + hash_map<rtx, bool> need_no_cmovs; /* If we newly set a CC before a cmov, we might need a temporary even though the compare will be removed by a later pass. Costing @@ -3214,6 +3216,9 @@ noce_convert_multiple_sets (struct noce_if_info *if_info) check_need_temps (then_bb, &need_temps, cond); + /* Identify swap-style idioms. */ + check_need_cmovs (then_bb, &need_no_cmovs); + hash_map<rtx, bool> temps_created; FOR_BB_INSNS (then_bb, insn) @@ -3229,10 +3234,8 @@ noce_convert_multiple_sets (struct noce_if_info *if_info) rtx new_val = SET_SRC (set); rtx old_val = target; - rtx dest = SET_DEST (set); - rtx temp; - if (need_temps.get (dest)) + if (need_temps.get (target)) { temp = gen_reg_rtx (GET_MODE (target)); temps_created.put (target, true); @@ -3241,18 +3244,11 @@ noce_convert_multiple_sets (struct noce_if_info *if_info) temp = target; /* If we were supposed to read from an earlier write in this block, - we've changed the register allocation. Rewire the read. While - we are looking, also try to catch a swap idiom. */ + we've changed the register allocation. Rewire the read. */ for (int i = count - 1; i >= 0; --i) if (reg_overlap_mentioned_p (new_val, targets[i])) { - /* Catch a "swap" style idiom. */ - if (find_reg_note (insn, REG_DEAD, new_val) != NULL_RTX) - /* The write to targets[i] is only live until the read - here. As the condition codes match, we can propagate - the set to here. */ - new_val = SET_SRC (single_set (unmodified_insns[i])); - else + if (find_reg_note (insn, REG_DEAD, new_val) == NULL_RTX) new_val = temporaries[i]; break; } @@ -3324,8 +3320,11 @@ noce_convert_multiple_sets (struct noce_if_info *if_info) { start_sequence (); - temp_dest1 = noce_emit_cmove (if_info, temp, cond_code, - x, y, new_val, old_val, NULL_RTX); + if (!need_no_cmovs.get (insn)) + temp_dest1 = noce_emit_cmove (if_info, temp, cond_code, + x, y, new_val, old_val, NULL_RTX); + else + noce_emit_move_insn (target, new_val); /* If we failed to expand the conditional move, drop out and don't try to continue. */ @@ -3346,13 +3345,16 @@ noce_convert_multiple_sets (struct noce_if_info *if_info) end_sequence (); } - /* Now try emitting one passing a non-canonicalized cc comparison. - This allows the backend to emit a cmov directly without additional + /* Now try emitting a cmov passing a non-canonicalized cc comparison. + This allows backends to emit a cmov directly without additional compare. */ { start_sequence (); - temp_dest2 = noce_emit_cmove (if_info, target, cond_code, - x, y, new_val, old_val, cc_cmp); + if (!need_no_cmovs.get (insn)) + temp_dest2 = noce_emit_cmove (if_info, target, cond_code, + x, y, new_val, old_val, cc_cmp); + else + noce_emit_move_insn (target, new_val); /* If we failed to expand the conditional move, drop out and don't try to continue. */ @@ -3931,6 +3933,7 @@ check_cond_move_block (basic_block bb, /* Check for which sets we need to emit temporaries to hold the destination of a conditional move. */ + static void check_need_temps (basic_block bb, hash_map<rtx, bool> *need_temp, rtx cond) { @@ -3938,7 +3941,7 @@ check_need_temps (basic_block bb, hash_map<rtx, bool> *need_temp, rtx cond) FOR_BB_INSNS (bb, insn) { - rtx set, dest; + rtx set, src, dest; if (!active_insn_p (insn)) continue; @@ -3947,12 +3950,68 @@ check_need_temps (basic_block bb, hash_map<rtx, bool> *need_temp, rtx cond) if (set == NULL_RTX) continue; + src = SET_SRC (set); dest = SET_DEST (set); /* noce_emit_cmove will emit the condition check every time it is called so we need a temp register if the destination is modified. */ if (reg_overlap_mentioned_p (dest, cond)) need_temp->put (dest, true); + + } +} + +/* Find local swap-style idioms in BB and mark the first insn (1) + that is only a temporary as not needing a conditional move as + it is going to be dead afterwards anyway. + + (1) int tmp = a; + a = b; + b = tmp; + + + ifcvt + --> + + load tmp,a + cmov a,b + cmov b,tmp */ + +static void +check_need_cmovs (basic_block bb, hash_map<rtx, bool> *need_cmov) +{ + rtx_insn *insn; + int count = 0; + auto_vec<rtx> insns; + auto_vec<rtx> dests; + + FOR_BB_INSNS (bb, insn) + { + rtx set, src, dest; + + if (!active_insn_p (insn)) + continue; + + set = single_set (insn); + if (set == NULL_RTX) + continue; + + src = SET_SRC (set); + dest = SET_DEST (set); + + for (int i = count - 1; i >= 0; --i) + { + if (reg_overlap_mentioned_p (src, dests[i]) + && find_reg_note (insn, REG_DEAD, src) != NULL_RTX) + { + need_cmov->put (insns[i], false); + } + } + + insns.safe_push (insn); + dests.safe_push (dest); + + count++; } } -- 2.17.0