aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorManolis Tsamis <manolis.tsamis@vrull.eu>2023-08-29 11:47:39 +0200
committerPhilipp Tomsich <philipp.tomsich@vrull.eu>2024-08-12 14:57:22 +0200
commitb219cbeda72d23b7ad6ff12cd159784b7ef00667 (patch)
treed4df405d34ee4d44a9cae1833ab4ac021a3e8775
parent72c9b5f438f22cca493b4e2a8a2a31ff61bf1477 (diff)
downloadgcc-b219cbeda72d23b7ad6ff12cd159784b7ef00667.zip
gcc-b219cbeda72d23b7ad6ff12cd159784b7ef00667.tar.gz
gcc-b219cbeda72d23b7ad6ff12cd159784b7ef00667.tar.bz2
ifcvt: Handle multiple rewired regs and refactor noce_convert_multiple_sets
The existing implementation of need_cmov_or_rewire and noce_convert_multiple_sets_1 assumes that sets are either REG or SUBREG. This commit enchances them so they can handle/rewire arbitrary set statements. To do that a new helper struct noce_multiple_sets_info is introduced which is used by noce_convert_multiple_sets and its helper functions. This results in cleaner function signatures, improved efficientcy (a number of vecs and hash set/map are replaced with a single vec of struct) and simplicity. gcc/ChangeLog: * ifcvt.cc (need_cmov_or_rewire): Renamed init_noce_multiple_sets_info. (init_noce_multiple_sets_info): Initialize noce_multiple_sets_info. (noce_convert_multiple_sets_1): Use noce_multiple_sets_info and handle rewiring of multiple registers. (noce_convert_multiple_sets): Updated to use noce_multiple_sets_info. * ifcvt.h (struct noce_multiple_sets_info): Introduce new struct noce_multiple_sets_info to store info for noce_convert_multiple_sets. gcc/testsuite/ChangeLog: * gcc.target/aarch64/ifcvt_multiple_sets_rewire.c: New test.
-rw-r--r--gcc/ifcvt.cc243
-rw-r--r--gcc/ifcvt.h16
-rw-r--r--gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_rewire.c20
3 files changed, 141 insertions, 138 deletions
diff --git a/gcc/ifcvt.cc b/gcc/ifcvt.cc
index f496a46e..3e25f30 100644
--- a/gcc/ifcvt.cc
+++ b/gcc/ifcvt.cc
@@ -98,14 +98,10 @@ static bool dead_or_predicable (basic_block, basic_block, basic_block,
edge, bool);
static void noce_emit_move_insn (rtx, rtx);
static rtx_insn *block_has_only_trap (basic_block);
-static void need_cmov_or_rewire (basic_block, hash_set<rtx_insn *> *,
- hash_map<rtx_insn *, int> *);
+static void init_noce_multiple_sets_info (basic_block,
+ auto_delete_vec<noce_multiple_sets_info> &);
static bool noce_convert_multiple_sets_1 (struct noce_if_info *,
- hash_set<rtx_insn *> *,
- hash_map<rtx_insn *, int> *,
- auto_vec<rtx> *,
- auto_vec<rtx> *,
- auto_vec<rtx_insn *> *, int *);
+ auto_delete_vec<noce_multiple_sets_info> &, int *);
/* Count the number of non-jump active insns in BB. */
@@ -3487,24 +3483,13 @@ noce_convert_multiple_sets (struct noce_if_info *if_info)
rtx x = XEXP (cond, 0);
rtx y = XEXP (cond, 1);
- /* The true targets for a conditional move. */
- auto_vec<rtx> targets;
- /* The temporaries introduced to allow us to not consider register
- overlap. */
- auto_vec<rtx> temporaries;
- /* The insns we've emitted. */
- auto_vec<rtx_insn *> unmodified_insns;
-
- hash_set<rtx_insn *> need_no_cmov;
- hash_map<rtx_insn *, int> rewired_src;
-
- need_cmov_or_rewire (then_bb, &need_no_cmov, &rewired_src);
+ auto_delete_vec<noce_multiple_sets_info> insn_info;
+ init_noce_multiple_sets_info (then_bb, insn_info);
int last_needs_comparison = -1;
bool ok = noce_convert_multiple_sets_1
- (if_info, &need_no_cmov, &rewired_src, &targets, &temporaries,
- &unmodified_insns, &last_needs_comparison);
+ (if_info, insn_info, &last_needs_comparison);
if (!ok)
return false;
@@ -3519,8 +3504,7 @@ noce_convert_multiple_sets (struct noce_if_info *if_info)
end_sequence ();
start_sequence ();
ok = noce_convert_multiple_sets_1
- (if_info, &need_no_cmov, &rewired_src, &targets, &temporaries,
- &unmodified_insns, &last_needs_comparison);
+ (if_info, insn_info, &last_needs_comparison);
/* Actually we should not fail anymore if we reached here,
but better still check. */
if (!ok)
@@ -3529,12 +3513,12 @@ noce_convert_multiple_sets (struct noce_if_info *if_info)
/* We must have seen some sort of insn to insert, otherwise we were
given an empty BB to convert, and we can't handle that. */
- gcc_assert (!unmodified_insns.is_empty ());
+ gcc_assert (!insn_info.is_empty ());
/* Now fixup the assignments. */
- for (unsigned i = 0; i < targets.length (); i++)
- if (targets[i] != temporaries[i])
- noce_emit_move_insn (targets[i], temporaries[i]);
+ for (unsigned i = 0; i < insn_info.length (); i++)
+ if (insn_info[i]->target != insn_info[i]->temporary)
+ noce_emit_move_insn (insn_info[i]->target, insn_info[i]->temporary);
/* Actually emit the sequence if it isn't too expensive. */
rtx_insn *seq = get_insns ();
@@ -3549,10 +3533,10 @@ noce_convert_multiple_sets (struct noce_if_info *if_info)
set_used_flags (insn);
/* Mark all our temporaries and targets as used. */
- for (unsigned i = 0; i < targets.length (); i++)
+ for (unsigned i = 0; i < insn_info.length (); i++)
{
- set_used_flags (temporaries[i]);
- set_used_flags (targets[i]);
+ set_used_flags (insn_info[i]->temporary);
+ set_used_flags (insn_info[i]->target);
}
set_used_flags (cond);
@@ -3571,7 +3555,7 @@ noce_convert_multiple_sets (struct noce_if_info *if_info)
return false;
emit_insn_before_setloc (seq, if_info->jump,
- INSN_LOCATION (unmodified_insns.last ()));
+ 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));
@@ -3592,20 +3576,12 @@ 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. In case a simple move sufficis the insn
- should be listed in NEED_NO_CMOV. The rewired-src cases should be
- specified via REWIRED_SRC. TARGETS, TEMPORARIES and UNMODIFIED_INSNS
- are specified and used in noce_convert_multiple_sets and should be passed
- to this function.. */
+/* 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. */
static bool
noce_convert_multiple_sets_1 (struct noce_if_info *if_info,
- hash_set<rtx_insn *> *need_no_cmov,
- hash_map<rtx_insn *, int> *rewired_src,
- auto_vec<rtx> *targets,
- auto_vec<rtx> *temporaries,
- auto_vec<rtx_insn *> *unmodified_insns,
+ auto_delete_vec<noce_multiple_sets_info> &insn_info,
int *last_needs_comparison)
{
basic_block then_bb = if_info->then_bb;
@@ -3624,11 +3600,6 @@ noce_convert_multiple_sets_1 (struct noce_if_info *if_info,
rtx_insn *insn;
int count = 0;
-
- targets->truncate (0);
- temporaries->truncate (0);
- unmodified_insns->truncate (0);
-
bool second_try = *last_needs_comparison != -1;
FOR_BB_INSNS (then_bb, insn)
@@ -3637,6 +3608,8 @@ noce_convert_multiple_sets_1 (struct noce_if_info *if_info,
if (!active_insn_p (insn))
continue;
+ noce_multiple_sets_info *info = insn_info[count];
+
rtx set = single_set (insn);
gcc_checking_assert (set);
@@ -3644,9 +3617,12 @@ noce_convert_multiple_sets_1 (struct noce_if_info *if_info,
rtx temp;
rtx new_val = SET_SRC (set);
- if (int *ii = rewired_src->get (insn))
- new_val = simplify_replace_rtx (new_val, (*targets)[*ii],
- (*temporaries)[*ii]);
+
+ int i, ii;
+ FOR_EACH_VEC_ELT (info->rewired_src, i, ii)
+ new_val = simplify_replace_rtx (new_val, insn_info[ii]->target,
+ insn_info[ii]->temporary);
+
rtx old_val = target;
/* As we are transforming
@@ -3687,7 +3663,7 @@ noce_convert_multiple_sets_1 (struct noce_if_info *if_info,
/* We have identified swap-style idioms before. A normal
set will need to be a cmov while the first instruction of a swap-style
idiom can be a regular move. This helps with costing. */
- bool need_cmov = !need_no_cmov->contains (insn);
+ bool need_cmov = info->need_cmov;
/* If we had a non-canonical conditional jump (i.e. one where
the fallthrough is to the "else" case) we need to reverse
@@ -3842,9 +3818,10 @@ noce_convert_multiple_sets_1 (struct noce_if_info *if_info,
/* Bookkeeping. */
count++;
- targets->safe_push (target);
- temporaries->safe_push (temp_dest);
- unmodified_insns->safe_push (insn);
+
+ info->target = target;
+ info->temporary = temp_dest;
+ info->unmodified_insn = insn;
}
/* Even if we did not actually need the comparison, we want to make sure
@@ -3852,11 +3829,84 @@ noce_convert_multiple_sets_1 (struct noce_if_info *if_info,
if (*last_needs_comparison == -1)
*last_needs_comparison = 0;
-
return 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
+ -->
+
+ tmp = a;
+ a = cond ? b : a_old;
+ b = cond ? tmp : b_old;
+
+ Additionally, store the index of insns like (2) when a subsequent
+ SET reads from their destination.
+
+ (2) int c = a;
+ int d = c;
+
+ ifcvt
+ -->
+ c = cond ? a : c_old;
+ d = cond ? d : c; // Need to use c rather than c_old here.
+*/
+
+static void
+init_noce_multiple_sets_info (basic_block bb,
+ auto_delete_vec<noce_multiple_sets_info> &insn_info)
+{
+ rtx_insn *insn;
+ int count = 0;
+ auto_vec<rtx> dests;
+ bitmap bb_live_out = df_get_live_out (bb);
+
+ /* Iterate over all SETs, storing the destinations in DEST.
+ - If we encounter a previously changed register,
+ rewire the read to the original source.
+ - If we encounter a SET that writes to a destination
+ that is not live after this block then the register
+ does not need to be moved conditionally. */
+ FOR_BB_INSNS (bb, insn)
+ {
+ if (!active_insn_p (insn))
+ continue;
+
+ noce_multiple_sets_info *info = new noce_multiple_sets_info;
+ info->target = NULL_RTX;
+ info->temporary = NULL_RTX;
+ info->unmodified_insn = NULL;
+ insn_info.safe_push (info);
+
+ rtx set = single_set (insn);
+ gcc_checking_assert (set);
+
+ rtx src = SET_SRC (set);
+ rtx dest = SET_DEST (set);
+
+ gcc_checking_assert (REG_P (dest));
+ info->need_cmov = bitmap_bit_p (bb_live_out, REGNO (dest));
+
+ /* Check if the current SET's source is the same
+ as any previously seen destination.
+ This is quadratic but the number of insns in BB
+ is bounded by PARAM_MAX_RTL_IF_CONVERSION_INSNS. */
+ for (int i = count - 1; i >= 0; --i)
+ if (reg_mentioned_p (dests[i], src))
+ insn_info[count]->rewired_src.safe_push (i);
+
+ dests.safe_push (dest);
+ count++;
+ }
+}
/* 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
@@ -4326,89 +4376,6 @@ check_cond_move_block (basic_block bb,
return 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
- -->
-
- tmp = a;
- a = cond ? b : a_old;
- b = cond ? tmp : b_old;
-
- Additionally, store the index of insns like (2) when a subsequent
- SET reads from their destination.
-
- (2) int c = a;
- int d = c;
-
- ifcvt
- -->
-
- c = cond ? a : c_old;
- d = cond ? d : c; // Need to use c rather than c_old here.
-*/
-
-static void
-need_cmov_or_rewire (basic_block bb,
- hash_set<rtx_insn *> *need_no_cmov,
- hash_map<rtx_insn *, int> *rewired_src)
-{
- rtx_insn *insn;
- int count = 0;
- auto_vec<rtx_insn *> insns;
- auto_vec<rtx> dests;
-
- /* Iterate over all SETs, storing the destinations
- in DEST.
- - If we hit a SET that reads from a destination
- that we have seen before and the corresponding register
- is dead afterwards, the register does not need to be
- moved conditionally.
- - If we encounter a previously changed register,
- rewire the read to the original source. */
- 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);
- if (SUBREG_P (src))
- src = SUBREG_REG (src);
- dest = SET_DEST (set);
-
- /* Check if the current SET's source is the same
- as any previously seen destination.
- This is quadratic but the number of insns in BB
- is bounded by PARAM_MAX_RTL_IF_CONVERSION_INSNS. */
- if (REG_P (src))
- for (int i = count - 1; i >= 0; --i)
- if (reg_overlap_mentioned_p (src, dests[i]))
- {
- if (find_reg_note (insn, REG_DEAD, src) != NULL_RTX)
- need_no_cmov->add (insns[i]);
- else
- rewired_src->put (insn, i);
- }
-
- insns.safe_push (insn);
- dests.safe_push (dest);
-
- count++;
- }
-}
-
/* Given a basic block BB suitable for conditional move conversion,
a condition COND, and pointer maps THEN_VALS and ELSE_VALS containing
the register values depending on COND, emit the insns in the block as
diff --git a/gcc/ifcvt.h b/gcc/ifcvt.h
index 37147f9..204bcf6 100644
--- a/gcc/ifcvt.h
+++ b/gcc/ifcvt.h
@@ -40,6 +40,22 @@ struct ce_if_block
int pass; /* Pass number. */
};
+struct noce_multiple_sets_info
+{
+ /* A list of indices to instructions that we need to rewire into this
+ instruction when we replace them with temporary conditional moves. */
+ auto_vec<int> rewired_src;
+ /* The true targets for a conditional move. */
+ rtx target;
+ /* The temporaries introduced to allow us to not consider register
+ overlap. */
+ rtx temporary;
+ /* The insns we've emitted. */
+ rtx_insn *unmodified_insn;
+ /* True if a simple move can be used instead of a conditional move. */
+ bool need_cmov;
+};
+
/* Used by noce_process_if_block to communicate with its subroutines.
The subroutines know that A and B may be evaluated freely. They
diff --git a/gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_rewire.c b/gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_rewire.c
new file mode 100644
index 0000000..448425f
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/ifcvt_multiple_sets_rewire.c
@@ -0,0 +1,20 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-rtl-ce1" } */
+
+void sink2(int, int);
+
+void cond1(int cond, int x, int y, int z)
+{
+ if (x)
+ {
+ x = y + z;
+ y = z + x;
+ }
+
+ sink2(x, y);
+}
+
+/* { dg-final { scan-assembler-times "csel\tw0, w0, w1" 1 } } */
+/* { dg-final { scan-assembler-times "csel\tw1, w3, w2" 1 } } */
+
+/* { dg-final { scan-rtl-dump-times "if-conversion succeeded through noce_convert_multiple_sets" 1 "ce1" } } */