aboutsummaryrefslogtreecommitdiff
path: root/gcc/tree-vect-loop-manip.cc
diff options
context:
space:
mode:
authorTamar Christina <tamar.christina@arm.com>2023-12-24 19:18:12 +0000
committerTamar Christina <tamar.christina@arm.com>2023-12-24 19:29:32 +0000
commit01f4251b8775c832a92d55e2df57c9ac72eaceef (patch)
treed422c6ed538b9f7f09d216409abaf768b6196fa4 /gcc/tree-vect-loop-manip.cc
parentf1dcc0fe371e3cb10d2cbe3f6c88db6f72edddda (diff)
downloadgcc-01f4251b8775c832a92d55e2df57c9ac72eaceef.zip
gcc-01f4251b8775c832a92d55e2df57c9ac72eaceef.tar.gz
gcc-01f4251b8775c832a92d55e2df57c9ac72eaceef.tar.bz2
middle-end: Support vectorization of loops with multiple exits.
Hi All, This patch adds initial support for early break vectorization in GCC. In other words it implements support for vectorization of loops with multiple exits. The support is added for any target that implements a vector cbranch optab, this includes both fully masked and non-masked targets. Depending on the operation, the vectorizer may also require support for boolean mask reductions using Inclusive OR/Bitwise AND. This is however only checked then the comparison would produce multiple statements. This also fully decouples the vectorizer's notion of exit from the existing loop infrastructure's exit. Before this patch the vectorizer always picked the natural loop latch connected exit as the main exit. After this patch the vectorizer is free to choose any exit it deems appropriate as the main exit. This means that even if the main exit is not countable (i.e. the termination condition could not be determined) we might still be able to vectorize should one of the other exits be countable. In such situations the loop is reflowed which enabled vectorization of many other loop forms. Concretely the kind of loops supported are of the forms: for (int i = 0; i < N; i++) { <statements1> if (<condition>) { ... <action>; } <statements2> } where <action> can be: - break - return - goto Any number of statements can be used before the <action> occurs. Since this is an initial version for GCC 14 it has the following limitations and features: - Only fixed sized iterations and buffers are supported. That is to say any vectors loaded or stored must be to statically allocated arrays with known sizes. N must also be known. This limitation is because our primary target for this optimization is SVE. For VLA SVE we can't easily do cross page iteraion checks. The result is likely to also not be beneficial. For that reason we punt support for variable buffers till we have First-Faulting support in GCC 15. - any stores in <statements1> should not be to the same objects as in <condition>. Loads are fine as long as they don't have the possibility to alias. More concretely, we block RAW dependencies when the intermediate value can't be separated fromt the store, or the store itself can't be moved. - Prologue peeling, alignment peelinig and loop versioning are supported. - Fully masked loops, unmasked loops and partially masked loops are supported - Any number of loop early exits are supported. - No support for epilogue vectorization. The only epilogue supported is the scalar final one. Peeling code supports it but the code motion code cannot find instructions to make the move in the epilog. - Early breaks are only supported for inner loop vectorization. With the help of IPA and LTO this still gets hit quite often. During bootstrap it hit rather frequently. Additionally TSVC s332, s481 and s482 all pass now since these are tests for support for early exit vectorization. This implementation does not support completely handling the early break inside the vector loop itself but instead supports adding checks such that if we know that we have to exit in the current iteration then we branch to scalar code to actually do the final VF iterations which handles all the code in <action>. For the scalar loop we know that whatever exit you take you have to perform at most VF iterations. For vector code we only case about the state of fully performed iteration and reset the scalar code to the (partially) remaining loop. That is to say, the first vector loop executes so long as the early exit isn't needed. Once the exit is taken, the scalar code will perform at most VF extra iterations. The exact number depending on peeling and iteration start and which exit was taken (natural or early). For this scalar loop, all early exits are treated the same. When we vectorize we move any statement not related to the early break itself and that would be incorrect to execute before the break (i.e. has side effects) to after the break. If this is not possible we decline to vectorize. The analysis and code motion also takes into account that it doesn't introduce a RAW dependency after the move of the stores. This means that we check at the start of iterations whether we are going to exit or not. During the analyis phase we check whether we are allowed to do this moving of statements. Also note that we only move the scalar statements, but only do so after peeling but just before we start transforming statements. With this the vector flow no longer necessarily needs to match that of the scalar code. In addition most of the infrastructure is in place to support general control flow safely, however we are punting this to GCC 15. Codegen: for e.g. unsigned vect_a[N]; unsigned vect_b[N]; unsigned test4(unsigned x) { unsigned ret = 0; for (int i = 0; i < N; i++) { vect_b[i] = x + i; if (vect_a[i] > x) break; vect_a[i] = x; } return ret; } We generate for Adv. SIMD: test4: adrp x2, .LC0 adrp x3, .LANCHOR0 dup v2.4s, w0 add x3, x3, :lo12:.LANCHOR0 movi v4.4s, 0x4 add x4, x3, 3216 ldr q1, [x2, #:lo12:.LC0] mov x1, 0 mov w2, 0 .p2align 3,,7 .L3: ldr q0, [x3, x1] add v3.4s, v1.4s, v2.4s add v1.4s, v1.4s, v4.4s cmhi v0.4s, v0.4s, v2.4s umaxp v0.4s, v0.4s, v0.4s fmov x5, d0 cbnz x5, .L6 add w2, w2, 1 str q3, [x1, x4] str q2, [x3, x1] add x1, x1, 16 cmp w2, 200 bne .L3 mov w7, 3 .L2: lsl w2, w2, 2 add x5, x3, 3216 add w6, w2, w0 sxtw x4, w2 ldr w1, [x3, x4, lsl 2] str w6, [x5, x4, lsl 2] cmp w0, w1 bcc .L4 add w1, w2, 1 str w0, [x3, x4, lsl 2] add w6, w1, w0 sxtw x1, w1 ldr w4, [x3, x1, lsl 2] str w6, [x5, x1, lsl 2] cmp w0, w4 bcc .L4 add w4, w2, 2 str w0, [x3, x1, lsl 2] sxtw x1, w4 add w6, w1, w0 ldr w4, [x3, x1, lsl 2] str w6, [x5, x1, lsl 2] cmp w0, w4 bcc .L4 str w0, [x3, x1, lsl 2] add w2, w2, 3 cmp w7, 3 beq .L4 sxtw x1, w2 add w2, w2, w0 ldr w4, [x3, x1, lsl 2] str w2, [x5, x1, lsl 2] cmp w0, w4 bcc .L4 str w0, [x3, x1, lsl 2] .L4: mov w0, 0 ret .p2align 2,,3 .L6: mov w7, 4 b .L2 and for SVE: test4: adrp x2, .LANCHOR0 add x2, x2, :lo12:.LANCHOR0 add x5, x2, 3216 mov x3, 0 mov w1, 0 cntw x4 mov z1.s, w0 index z0.s, #0, #1 ptrue p1.b, all ptrue p0.s, all .p2align 3,,7 .L3: ld1w z2.s, p1/z, [x2, x3, lsl 2] add z3.s, z0.s, z1.s cmplo p2.s, p0/z, z1.s, z2.s b.any .L2 st1w z3.s, p1, [x5, x3, lsl 2] add w1, w1, 1 st1w z1.s, p1, [x2, x3, lsl 2] add x3, x3, x4 incw z0.s cmp w3, 803 bls .L3 .L5: mov w0, 0 ret .p2align 2,,3 .L2: cntw x5 mul w1, w1, w5 cbz w5, .L5 sxtw x1, w1 sub w5, w5, #1 add x5, x5, x1 add x6, x2, 3216 b .L6 .p2align 2,,3 .L14: str w0, [x2, x1, lsl 2] cmp x1, x5 beq .L5 mov x1, x4 .L6: ldr w3, [x2, x1, lsl 2] add w4, w0, w1 str w4, [x6, x1, lsl 2] add x4, x1, 1 cmp w0, w3 bcs .L14 mov w0, 0 ret On the workloads this work is based on we see between 2-3x performance uplift using this patch. Follow up plan: - Boolean vectorization has several shortcomings. I've filed PR110223 with the bigger ones that cause vectorization to fail with this patch. - SLP support. This is planned for GCC 15 as for majority of the cases build SLP itself fails. This means I'll need to spend time in making this more robust first. Additionally it requires: * Adding support for vectorizing CFG (gconds) * Support for CFG to differ between vector and scalar loops. Both of which would be disruptive to the tree and I suspect I'll be handling fallouts from this patch for a while. So I plan to work on the surrounding building blocks first for the remainder of the year. Additionally it also contains reduced cases from issues found running over various codebases. Bootstrapped Regtested on aarch64-none-linux-gnu and no issues. Also regtested with: -march=armv8.3-a+sve -march=armv8.3-a+nosve -march=armv9-a -mcpu=neoverse-v1 -mcpu=neoverse-n2 Bootstrapped Regtested x86_64-pc-linux-gnu and no issues. Bootstrap and Regtest on arm-none-linux-gnueabihf and no issues. gcc/ChangeLog: * tree-if-conv.cc (idx_within_array_bound): Expose. * tree-vect-data-refs.cc (vect_analyze_early_break_dependences): New. (vect_analyze_data_ref_dependences): Use it. * tree-vect-loop-manip.cc (vect_iv_increment_position): New. (vect_set_loop_controls_directly, vect_set_loop_condition_partial_vectors, vect_set_loop_condition_partial_vectors_avx512, vect_set_loop_condition_normal): Support multiple exits. (slpeel_tree_duplicate_loop_to_edge_cfg): Support LCSAA peeling for multiple exits. (slpeel_can_duplicate_loop_p): Change vectorizer from looking at BB count and instead look at loop shape. (vect_update_ivs_after_vectorizer): Drop asserts. (vect_gen_vector_loop_niters_mult_vf): Support peeled vector iterations. (vect_do_peeling): Support multiple exits. (vect_loop_versioning): Likewise. * tree-vect-loop.cc (_loop_vec_info::_loop_vec_info): Initialise early_breaks. (vect_analyze_loop_form): Support loop flows with more than single BB loop body. (vect_create_loop_vinfo): Support niters analysis for multiple exits. (vect_analyze_loop): Likewise. (vect_get_vect_def): New. (vect_create_epilog_for_reduction): Support early exit reductions. (vectorizable_live_operation_1): New. (find_connected_edge): New. (vectorizable_live_operation): Support early exit live operations. (move_early_exit_stmts): New. (vect_transform_loop): Use it. * tree-vect-patterns.cc (vect_init_pattern_stmt): Support gcond. (vect_recog_bitfield_ref_pattern): Support gconds and bools. (vect_recog_gcond_pattern): New. (possible_vector_mask_operation_p): Support gcond masks. (vect_determine_mask_precision): Likewise. (vect_mark_pattern_stmts): Set gcond def type. (can_vectorize_live_stmts): Force early break inductions to be live. * tree-vect-stmts.cc (vect_stmt_relevant_p): Add relevancy analysis for early breaks. (vect_mark_stmts_to_be_vectorized): Process gcond usage. (perm_mask_for_reverse): Expose. (vectorizable_comparison_1): New. (vectorizable_early_exit): New. (vect_analyze_stmt): Support early break and gcond. (vect_transform_stmt): Likewise. (vect_is_simple_use): Likewise. (vect_get_vector_types_for_stmt): Likewise. * tree-vectorizer.cc (pass_vectorize::execute): Update exits for value numbering. * tree-vectorizer.h (enum vect_def_type): Add vect_condition_def. (LOOP_VINFO_EARLY_BREAKS, LOOP_VINFO_EARLY_BRK_STORES, LOOP_VINFO_EARLY_BREAKS_VECT_PEELED, LOOP_VINFO_EARLY_BRK_DEST_BB, LOOP_VINFO_EARLY_BRK_VUSES): New. (is_loop_header_bb_p): Drop assert. (class loop): Add early_breaks, early_break_stores, early_break_dest_bb, early_break_vuses. (vect_iv_increment_position, perm_mask_for_reverse, ref_within_array_bound): New. (slpeel_tree_duplicate_loop_to_edge_cfg): Update for early breaks.
Diffstat (limited to 'gcc/tree-vect-loop-manip.cc')
-rw-r--r--gcc/tree-vect-loop-manip.cc331
1 files changed, 267 insertions, 64 deletions
diff --git a/gcc/tree-vect-loop-manip.cc b/gcc/tree-vect-loop-manip.cc
index bcd90a3..295e1c9 100644
--- a/gcc/tree-vect-loop-manip.cc
+++ b/gcc/tree-vect-loop-manip.cc
@@ -448,6 +448,20 @@ vect_adjust_loop_lens_control (tree iv_type, gimple_seq *seq,
}
}
+/* Stores the standard position for induction variable increment in belonging to
+ LOOP_EXIT (just before the exit condition of the given exit to BSI.
+ INSERT_AFTER is set to true if the increment should be inserted after
+ *BSI. */
+
+void
+vect_iv_increment_position (edge loop_exit, gimple_stmt_iterator *bsi,
+ bool *insert_after)
+{
+ basic_block bb = loop_exit->src;
+ *bsi = gsi_last_bb (bb);
+ *insert_after = false;
+}
+
/* Helper for vect_set_loop_condition_partial_vectors. Generate definitions
for all the rgroup controls in RGC and return a control that is nonzero
when the loop needs to iterate. Add any new preheader statements to
@@ -531,7 +545,8 @@ vect_set_loop_controls_directly (class loop *loop, loop_vec_info loop_vinfo,
tree index_before_incr, index_after_incr;
gimple_stmt_iterator incr_gsi;
bool insert_after;
- standard_iv_increment_position (loop, &incr_gsi, &insert_after);
+ edge exit_e = LOOP_VINFO_IV_EXIT (loop_vinfo);
+ vect_iv_increment_position (exit_e, &incr_gsi, &insert_after);
if (LOOP_VINFO_USING_DECREMENTING_IV_P (loop_vinfo))
{
/* Create an IV that counts down from niters_total and whose step
@@ -936,7 +951,18 @@ vect_set_loop_condition_partial_vectors (class loop *loop, edge exit_edge,
if (final_iv)
{
- gassign *assign = gimple_build_assign (final_iv, orig_niters);
+ gassign *assign;
+ /* If vectorizing an inverted early break loop we have to restart the
+ scalar loop at niters - vf. This matches what we do in
+ vect_gen_vector_loop_niters_mult_vf for non-masked loops. */
+ if (LOOP_VINFO_EARLY_BREAKS_VECT_PEELED (loop_vinfo))
+ {
+ tree ftype = TREE_TYPE (orig_niters);
+ tree vf = build_int_cst (ftype, LOOP_VINFO_VECT_FACTOR (loop_vinfo));
+ assign = gimple_build_assign (final_iv, MINUS_EXPR, orig_niters, vf);
+ }
+ else
+ assign = gimple_build_assign (final_iv, orig_niters);
gsi_insert_on_edge_immediate (exit_edge, assign);
}
@@ -1017,7 +1043,7 @@ vect_set_loop_condition_partial_vectors_avx512 (class loop *loop,
tree index_before_incr, index_after_incr;
gimple_stmt_iterator incr_gsi;
bool insert_after;
- standard_iv_increment_position (loop, &incr_gsi, &insert_after);
+ vect_iv_increment_position (exit_edge, &incr_gsi, &insert_after);
create_iv (niters_adj, MINUS_EXPR, iv_step, NULL_TREE, loop,
&incr_gsi, insert_after, &index_before_incr,
&index_after_incr);
@@ -1173,8 +1199,19 @@ vect_set_loop_condition_partial_vectors_avx512 (class loop *loop,
if (final_iv)
{
- gassign *assign = gimple_build_assign (final_iv, orig_niters);
- gsi_insert_on_edge_immediate (single_exit (loop), assign);
+ gassign *assign;
+ /* If vectorizing an inverted early break loop we have to restart the
+ scalar loop at niters - vf. This matches what we do in
+ vect_gen_vector_loop_niters_mult_vf for non-masked loops. */
+ if (LOOP_VINFO_EARLY_BREAKS_VECT_PEELED (loop_vinfo))
+ {
+ tree ftype = TREE_TYPE (orig_niters);
+ tree vf = build_int_cst (ftype, LOOP_VINFO_VECT_FACTOR (loop_vinfo));
+ assign = gimple_build_assign (final_iv, MINUS_EXPR, orig_niters, vf);
+ }
+ else
+ assign = gimple_build_assign (final_iv, orig_niters);
+ gsi_insert_on_edge_immediate (exit_edge, assign);
}
return cond_stmt;
@@ -1278,7 +1315,7 @@ vect_set_loop_condition_normal (loop_vec_info /* loop_vinfo */, edge exit_edge,
}
}
- standard_iv_increment_position (loop, &incr_gsi, &insert_after);
+ vect_iv_increment_position (exit_edge, &incr_gsi, &insert_after);
create_iv (init, PLUS_EXPR, step, NULL_TREE, loop,
&incr_gsi, insert_after, &indx_before_incr, &indx_after_incr);
indx_after_incr = force_gimple_operand_gsi (&loop_cond_gsi, indx_after_incr,
@@ -1403,13 +1440,16 @@ vect_set_loop_condition (class loop *loop, edge loop_e, loop_vec_info loop_vinfo
copies remains the same.
If UPDATED_DOMS is not NULL it is update with the list of basic blocks whoms
- dominators were updated during the peeling. */
+ dominators were updated during the peeling. When doing early break vectorization
+ then LOOP_VINFO needs to be provided and is used to keep track of any newly created
+ memory references that need to be updated should we decide to vectorize. */
class loop *
slpeel_tree_duplicate_loop_to_edge_cfg (class loop *loop, edge loop_exit,
class loop *scalar_loop,
edge scalar_exit, edge e, edge *new_e,
- bool flow_loops)
+ bool flow_loops,
+ vec<basic_block> *updated_doms)
{
class loop *new_loop;
basic_block *new_bbs, *bbs, *pbbs;
@@ -1526,7 +1566,9 @@ slpeel_tree_duplicate_loop_to_edge_cfg (class loop *loop, edge loop_exit,
}
auto loop_exits = get_loop_exit_edges (loop);
+ bool multiple_exits_p = loop_exits.length () > 1;
auto_vec<basic_block> doms;
+ class loop *update_loop = NULL;
if (at_exit) /* Add the loop copy at exit. */
{
@@ -1536,39 +1578,65 @@ slpeel_tree_duplicate_loop_to_edge_cfg (class loop *loop, edge loop_exit,
flush_pending_stmts (new_exit);
}
+ bool multiple_exits_p = loop_exits.length () > 1;
+ basic_block main_loop_exit_block = new_preheader;
+ basic_block alt_loop_exit_block = NULL;
+ /* Create intermediate edge for main exit. But only useful for early
+ exits. */
+ if (multiple_exits_p)
+ {
+ edge loop_e = single_succ_edge (new_preheader);
+ new_preheader = split_edge (loop_e);
+ }
+
auto_vec <gimple *> new_phis;
hash_map <tree, tree> new_phi_args;
/* First create the empty phi nodes so that when we flush the
statements they can be filled in. However because there is no order
between the PHI nodes in the exits and the loop headers we need to
order them base on the order of the two headers. First record the new
- phi nodes. */
- for (auto gsi_from = gsi_start_phis (scalar_exit->dest);
+ phi nodes. Then redirect the edges and flush the changes. This writes
+ out the new SSA names. */
+ for (auto gsi_from = gsi_start_phis (loop_exit->dest);
!gsi_end_p (gsi_from); gsi_next (&gsi_from))
{
gimple *from_phi = gsi_stmt (gsi_from);
tree new_res = copy_ssa_name (gimple_phi_result (from_phi));
- gphi *res = create_phi_node (new_res, new_preheader);
+ gphi *res = create_phi_node (new_res, main_loop_exit_block);
new_phis.safe_push (res);
}
- /* Then redirect the edges and flush the changes. This writes out the new
- SSA names. */
- for (edge exit : loop_exits)
+ for (auto exit : loop_exits)
{
- edge temp_e = redirect_edge_and_branch (exit, new_preheader);
- flush_pending_stmts (temp_e);
+ basic_block dest = main_loop_exit_block;
+ if (exit != loop_exit)
+ {
+ if (!alt_loop_exit_block)
+ {
+ alt_loop_exit_block = split_edge (exit);
+ edge res = redirect_edge_and_branch (
+ single_succ_edge (alt_loop_exit_block),
+ new_preheader);
+ flush_pending_stmts (res);
+ continue;
+ }
+ dest = alt_loop_exit_block;
+ }
+ edge e = redirect_edge_and_branch (exit, dest);
+ flush_pending_stmts (e);
}
+
/* Record the new SSA names in the cache so that we can skip materializing
them again when we fill in the rest of the LCSSA variables. */
for (auto phi : new_phis)
{
- tree new_arg = gimple_phi_arg (phi, 0)->def;
+ tree new_arg = gimple_phi_arg (phi, loop_exit->dest_idx)->def;
if (!SSA_VAR_P (new_arg))
continue;
+
/* If the PHI MEM node dominates the loop then we shouldn't create
- a new LC-SSSA PHI for it in the intermediate block. */
+ a new LC-SSSA PHI for it in the intermediate block. */
/* A MEM phi that consitutes a new DEF for the vUSE chain can either
be a .VDEF or a PHI that operates on MEM. And said definition
must not be inside the main loop. Or we must be a parameter.
@@ -1584,6 +1652,9 @@ slpeel_tree_duplicate_loop_to_edge_cfg (class loop *loop, edge loop_exit,
remove_phi_node (&gsi, true);
continue;
}
+
+ /* If we decide to remove the PHI node we should also not
+ rematerialize it later on. */
new_phi_args.put (new_arg, gimple_phi_result (phi));
if (TREE_CODE (new_arg) != SSA_NAME)
@@ -1595,34 +1666,77 @@ slpeel_tree_duplicate_loop_to_edge_cfg (class loop *loop, edge loop_exit,
preheader block and still find the right LC nodes. */
edge loop_entry = single_succ_edge (new_preheader);
if (flow_loops)
- for (auto gsi_from = gsi_start_phis (loop->header),
- gsi_to = gsi_start_phis (new_loop->header);
- !gsi_end_p (gsi_from) && !gsi_end_p (gsi_to);
- gsi_next (&gsi_from), gsi_next (&gsi_to))
- {
- gimple *from_phi = gsi_stmt (gsi_from);
- gimple *to_phi = gsi_stmt (gsi_to);
- tree new_arg = PHI_ARG_DEF_FROM_EDGE (from_phi,
- loop_latch_edge (loop));
+ {
+ bool peeled_iters = single_pred (loop->latch) != loop_exit->src;
+ /* Link through the main exit first. */
+ for (auto gsi_from = gsi_start_phis (loop->header),
+ gsi_to = gsi_start_phis (new_loop->header);
+ !gsi_end_p (gsi_from) && !gsi_end_p (gsi_to);
+ gsi_next (&gsi_from), gsi_next (&gsi_to))
+ {
+ gimple *from_phi = gsi_stmt (gsi_from);
+ gimple *to_phi = gsi_stmt (gsi_to);
+ tree new_arg = PHI_ARG_DEF_FROM_EDGE (from_phi,
+ loop_latch_edge (loop));
+
+ /* Check if we've already created a new phi node during edge
+ redirection. If we have, only propagate the value
+ downwards. */
+ if (tree *res = new_phi_args.get (new_arg))
+ {
+ if (multiple_exits_p)
+ new_arg = *res;
+ else
+ {
+ adjust_phi_and_debug_stmts (to_phi, loop_entry, *res);
+ continue;
+ }
+ }
+ /* If we have multiple exits and the vector loop is peeled then we
+ need to use the value at start of loop. */
+ if (peeled_iters)
+ {
+ tree tmp_arg = gimple_phi_result (from_phi);
+ if (!new_phi_args.get (tmp_arg))
+ new_arg = tmp_arg;
+ }
- /* Check if we've already created a new phi node during edge
- redirection. If we have, only propagate the value downwards. */
- if (tree *res = new_phi_args.get (new_arg))
- {
- adjust_phi_and_debug_stmts (to_phi, loop_entry, *res);
- continue;
- }
+ tree new_res = copy_ssa_name (gimple_phi_result (from_phi));
+ gphi *lcssa_phi = create_phi_node (new_res, new_preheader);
- tree new_res = copy_ssa_name (gimple_phi_result (from_phi));
- gphi *lcssa_phi = create_phi_node (new_res, new_preheader);
+ /* Otherwise, main loop exit should use the final iter value. */
+ SET_PHI_ARG_DEF (lcssa_phi, loop_exit->dest_idx, new_arg);
- /* Main loop exit should use the final iter value. */
- add_phi_arg (lcssa_phi, new_arg, loop_exit, UNKNOWN_LOCATION);
+ adjust_phi_and_debug_stmts (to_phi, loop_entry, new_res);
+ }
- adjust_phi_and_debug_stmts (to_phi, loop_entry, new_res);
- }
+ set_immediate_dominator (CDI_DOMINATORS, main_loop_exit_block,
+ loop_exit->src);
- set_immediate_dominator (CDI_DOMINATORS, new_preheader, e->src);
+ /* Now link the alternative exits. */
+ if (multiple_exits_p)
+ {
+ set_immediate_dominator (CDI_DOMINATORS, new_preheader,
+ main_loop_exit_block);
+ for (auto gsi_from = gsi_start_phis (loop->header),
+ gsi_to = gsi_start_phis (new_preheader);
+ !gsi_end_p (gsi_from) && !gsi_end_p (gsi_to);
+ gsi_next (&gsi_from), gsi_next (&gsi_to))
+ {
+ gimple *from_phi = gsi_stmt (gsi_from);
+ gimple *to_phi = gsi_stmt (gsi_to);
+
+ tree alt_arg = gimple_phi_result (from_phi);
+ edge main_e = single_succ_edge (alt_loop_exit_block);
+ for (edge e : loop_exits)
+ if (e != loop_exit)
+ SET_PHI_ARG_DEF (to_phi, main_e->dest_idx, alt_arg);
+ }
+
+ set_immediate_dominator (CDI_DOMINATORS, new_preheader,
+ loop->header);
+ }
+ }
if (was_imm_dom || duplicate_outer_loop)
set_immediate_dominator (CDI_DOMINATORS, exit_dest, new_exit->src);
@@ -1634,6 +1748,21 @@ slpeel_tree_duplicate_loop_to_edge_cfg (class loop *loop, edge loop_exit,
delete_basic_block (preheader);
set_immediate_dominator (CDI_DOMINATORS, scalar_loop->header,
loop_preheader_edge (scalar_loop)->src);
+
+ /* Finally after wiring the new epilogue we need to update its main exit
+ to the original function exit we recorded. Other exits are already
+ correct. */
+ if (multiple_exits_p)
+ {
+ update_loop = new_loop;
+ for (edge e : get_loop_exit_edges (loop))
+ doms.safe_push (e->dest);
+ doms.safe_push (exit_dest);
+
+ /* Likely a fall-through edge, so update if needed. */
+ if (single_succ_p (exit_dest))
+ doms.safe_push (single_succ (exit_dest));
+ }
}
else /* Add the copy at entry. */
{
@@ -1681,6 +1810,34 @@ slpeel_tree_duplicate_loop_to_edge_cfg (class loop *loop, edge loop_exit,
delete_basic_block (new_preheader);
set_immediate_dominator (CDI_DOMINATORS, new_loop->header,
loop_preheader_edge (new_loop)->src);
+
+ if (multiple_exits_p)
+ update_loop = loop;
+ }
+
+ if (multiple_exits_p)
+ {
+ for (edge e : get_loop_exit_edges (update_loop))
+ {
+ edge ex;
+ edge_iterator ei;
+ FOR_EACH_EDGE (ex, ei, e->dest->succs)
+ {
+ /* Find the first non-fallthrough block as fall-throughs can't
+ dominate other blocks. */
+ if (single_succ_p (ex->dest))
+ {
+ doms.safe_push (ex->dest);
+ ex = single_succ_edge (ex->dest);
+ }
+ doms.safe_push (ex->dest);
+ }
+ doms.safe_push (e->dest);
+ }
+
+ iterate_fix_dominators (CDI_DOMINATORS, doms, false);
+ if (updated_doms)
+ updated_doms->safe_splice (doms);
}
free (new_bbs);
@@ -1755,12 +1912,10 @@ slpeel_can_duplicate_loop_p (const class loop *loop, const_edge exit_e,
edge entry_e = loop_preheader_edge (loop);
gcond *orig_cond = get_loop_exit_condition (exit_e);
gimple_stmt_iterator loop_exit_gsi = gsi_last_bb (exit_e->src);
- unsigned int num_bb = loop->inner? 5 : 2;
/* All loops have an outer scope; the only case loop->outer is NULL is for
the function itself. */
if (!loop_outer (loop)
- || loop->num_nodes != num_bb
|| !empty_block_p (loop->latch)
|| !exit_e
/* Verify that new loop exit condition can be trivially modified. */
@@ -2049,12 +2204,8 @@ vect_update_ivs_after_vectorizer (loop_vec_info loop_vinfo,
gphi_iterator gsi, gsi1;
class loop *loop = LOOP_VINFO_LOOP (loop_vinfo);
basic_block update_bb = update_e->dest;
-
basic_block exit_bb = LOOP_VINFO_IV_EXIT (loop_vinfo)->dest;
-
- /* Make sure there exists a single-predecessor exit bb: */
- gcc_assert (single_pred_p (exit_bb));
- gcc_assert (single_succ_edge (exit_bb) == update_e);
+ gimple_stmt_iterator last_gsi = gsi_last_bb (exit_bb);
for (gsi = gsi_start_phis (loop->header), gsi1 = gsi_start_phis (update_bb);
!gsi_end_p (gsi) && !gsi_end_p (gsi1);
@@ -2064,7 +2215,6 @@ vect_update_ivs_after_vectorizer (loop_vec_info loop_vinfo,
tree step_expr, off;
tree type;
tree var, ni, ni_name;
- gimple_stmt_iterator last_gsi;
gphi *phi = gsi.phi ();
gphi *phi1 = gsi1.phi ();
@@ -2100,7 +2250,8 @@ vect_update_ivs_after_vectorizer (loop_vec_info loop_vinfo,
{
tree stype = TREE_TYPE (step_expr);
off = fold_build2 (MULT_EXPR, stype,
- fold_convert (stype, niters), step_expr);
+ fold_convert (stype, niters), step_expr);
+
if (POINTER_TYPE_P (type))
ni = fold_build_pointer_plus (init_expr, off);
else
@@ -2119,9 +2270,9 @@ vect_update_ivs_after_vectorizer (loop_vec_info loop_vinfo,
var = create_tmp_var (type, "tmp");
- last_gsi = gsi_last_bb (exit_bb);
gimple_seq new_stmts = NULL;
ni_name = force_gimple_operand (ni, &new_stmts, false, var);
+
/* Exit_bb shouldn't be empty. */
if (!gsi_end_p (last_gsi))
{
@@ -2619,11 +2770,19 @@ vect_gen_vector_loop_niters_mult_vf (loop_vec_info loop_vinfo,
int vf = LOOP_VINFO_VECT_FACTOR (loop_vinfo).to_constant ();
tree type = TREE_TYPE (niters_vector);
tree log_vf = build_int_cst (type, exact_log2 (vf));
+ tree tree_vf = build_int_cst (type, vf);
basic_block exit_bb = LOOP_VINFO_IV_EXIT (loop_vinfo)->dest;
gcc_assert (niters_vector_mult_vf_ptr != NULL);
tree niters_vector_mult_vf = fold_build2 (LSHIFT_EXPR, type,
niters_vector, log_vf);
+
+ /* If we've peeled a vector iteration then subtract one full vector
+ iteration. */
+ if (LOOP_VINFO_EARLY_BREAKS_VECT_PEELED (loop_vinfo))
+ niters_vector_mult_vf = fold_build2 (MINUS_EXPR, type,
+ niters_vector_mult_vf, tree_vf);
+
if (!is_gimple_val (niters_vector_mult_vf))
{
tree var = create_tmp_var (type, "niters_vector_mult_vf");
@@ -2864,6 +3023,12 @@ vect_do_peeling (loop_vec_info loop_vinfo, tree niters, tree nitersm1,
bound_epilog += vf - 1;
if (LOOP_VINFO_PEELING_FOR_GAPS (loop_vinfo))
bound_epilog += 1;
+
+ /* For early breaks the scalar loop needs to execute at most VF times
+ to find the element that caused the break. */
+ if (LOOP_VINFO_EARLY_BREAKS (loop_vinfo))
+ bound_epilog = vf;
+
bool epilog_peeling = maybe_ne (bound_epilog, 0U);
poly_uint64 bound_scalar = bound_epilog;
@@ -2998,14 +3163,17 @@ vect_do_peeling (loop_vec_info loop_vinfo, tree niters, tree nitersm1,
bound_prolog + bound_epilog)
: (!LOOP_REQUIRES_VERSIONING (loop_vinfo)
|| vect_epilogues));
+
/* Epilog loop must be executed if the number of iterations for epilog
loop is known at compile time, otherwise we need to add a check at
the end of vector loop and skip to the end of epilog loop. */
bool skip_epilog = (prolog_peeling < 0
|| !LOOP_VINFO_NITERS_KNOWN_P (loop_vinfo)
|| !vf.is_constant ());
- /* PEELING_FOR_GAPS is special because epilog loop must be executed. */
- if (LOOP_VINFO_PEELING_FOR_GAPS (loop_vinfo))
+ /* PEELING_FOR_GAPS and peeling for early breaks are special because epilog
+ loop must be executed. */
+ if (LOOP_VINFO_PEELING_FOR_GAPS (loop_vinfo)
+ || LOOP_VINFO_EARLY_BREAKS (loop_vinfo))
skip_epilog = false;
class loop *scalar_loop = LOOP_VINFO_SCALAR_LOOP (loop_vinfo);
@@ -3134,11 +3302,14 @@ vect_do_peeling (loop_vec_info loop_vinfo, tree niters, tree nitersm1,
epilog = vect_epilogues ? get_loop_copy (loop) : scalar_loop;
edge epilog_e = vect_epilogues ? e : scalar_e;
edge new_epilog_e = NULL;
- epilog = slpeel_tree_duplicate_loop_to_edge_cfg (loop, e, epilog,
- epilog_e, e,
- &new_epilog_e);
+ auto_vec<basic_block> doms;
+ epilog
+ = slpeel_tree_duplicate_loop_to_edge_cfg (loop, e, epilog, epilog_e, e,
+ &new_epilog_e, true, &doms);
+
LOOP_VINFO_EPILOGUE_IV_EXIT (loop_vinfo) = new_epilog_e;
gcc_assert (epilog);
+ gcc_assert (new_epilog_e);
epilog->force_vectorize = false;
bb_before_epilog = loop_preheader_edge (epilog)->src;
@@ -3189,10 +3360,11 @@ vect_do_peeling (loop_vec_info loop_vinfo, tree niters, tree nitersm1,
/* Only need to handle basic block before epilog loop if it's not
the guard_bb, which is the case when skip_vector is true. */
- if (guard_bb != bb_before_epilog)
+ if (guard_bb != bb_before_epilog && single_pred_p (bb_before_epilog))
bb_before_epilog->count = single_pred_edge (bb_before_epilog)->count ();
bb_before_epilog = loop_preheader_edge (epilog)->src;
}
+
/* If loop is peeled for non-zero constant times, now niters refers to
orig_niters - prolog_peeling, it won't overflow even the orig_niters
overflows. */
@@ -3216,13 +3388,22 @@ vect_do_peeling (loop_vec_info loop_vinfo, tree niters, tree nitersm1,
niters_vector_mult_vf steps. */
gcc_checking_assert (vect_can_advance_ivs_p (loop_vinfo));
update_e = skip_vector ? e : loop_preheader_edge (epilog);
- vect_update_ivs_after_vectorizer (loop_vinfo, niters_vector_mult_vf,
- update_e);
-
- if (skip_epilog)
+ if (LOOP_VINFO_EARLY_BREAKS (loop_vinfo))
+ update_e = single_succ_edge (LOOP_VINFO_IV_EXIT (loop_vinfo)->dest);
+
+ /* If we have a peeled vector iteration, all exits are the same, leave it
+ and so the main exit needs to be treated the same as the alternative
+ exits in that we leave their updates to vectorizable_live_operations.
+ */
+ if (!LOOP_VINFO_EARLY_BREAKS_VECT_PEELED (loop_vinfo))
+ vect_update_ivs_after_vectorizer (loop_vinfo, niters_vector_mult_vf,
+ update_e);
+
+ if (skip_epilog || LOOP_VINFO_EARLY_BREAKS (loop_vinfo))
{
guard_cond = fold_build2 (EQ_EXPR, boolean_type_node,
niters, niters_vector_mult_vf);
+
guard_bb = LOOP_VINFO_IV_EXIT (loop_vinfo)->dest;
edge epilog_e = LOOP_VINFO_EPILOGUE_IV_EXIT (loop_vinfo);
guard_to = epilog_e->dest;
@@ -3230,6 +3411,7 @@ vect_do_peeling (loop_vec_info loop_vinfo, tree niters, tree nitersm1,
skip_vector ? anchor : guard_bb,
prob_epilog.invert (),
irred_flag);
+ doms.safe_push (guard_to);
if (vect_epilogues)
epilogue_vinfo->skip_this_loop_edge = guard_e;
edge main_iv = LOOP_VINFO_IV_EXIT (loop_vinfo);
@@ -3268,10 +3450,20 @@ vect_do_peeling (loop_vec_info loop_vinfo, tree niters, tree nitersm1,
scale_loop_profile (epilog, prob_epilog, -1);
}
+ /* Recalculate the dominators after adding the guard edge. */
+ if (LOOP_VINFO_EARLY_BREAKS (loop_vinfo))
+ iterate_fix_dominators (CDI_DOMINATORS, doms, false);
+
unsigned HOST_WIDE_INT bound;
if (bound_scalar.is_constant (&bound))
{
gcc_assert (bound != 0);
+ /* Adjust the upper bound by the extra peeled vector iteration if we
+ are an epilogue of an peeled vect loop and not VLA. For VLA the
+ loop bounds are unknown. */
+ if (LOOP_VINFO_EARLY_BREAKS_VECT_PEELED (loop_vinfo)
+ && vf.is_constant ())
+ bound += vf.to_constant ();
/* -1 to convert loop iterations to latch iterations. */
record_niter_bound (epilog, bound - 1, false, true);
scale_loop_profile (epilog, profile_probability::always (),
@@ -3890,12 +4082,23 @@ vect_loop_versioning (loop_vec_info loop_vinfo,
If loop versioning wasn't done from loop, but scalar_loop instead,
merge_bb will have already just a single successor. */
- merge_bb = single_exit (loop_to_version)->dest;
+ /* When the loop has multiple exits then we can only version itself.
+ This is denoted by loop_to_version == loop. In this case we can
+ do the versioning by selecting the exit edge the vectorizer is
+ currently using. */
+ edge exit_edge;
+ if (loop_to_version == loop)
+ exit_edge = LOOP_VINFO_IV_EXIT (loop_vinfo);
+ else
+ exit_edge = single_exit (loop_to_version);
+
+ gcc_assert (exit_edge);
+ merge_bb = exit_edge->dest;
if (EDGE_COUNT (merge_bb->preds) >= 2)
{
gcc_assert (EDGE_COUNT (merge_bb->preds) >= 2);
- new_exit_bb = split_edge (single_exit (loop_to_version));
- new_exit_e = single_exit (loop_to_version);
+ new_exit_bb = split_edge (exit_edge);
+ new_exit_e = exit_edge;
e = EDGE_SUCC (new_exit_bb, 0);
for (gsi = gsi_start_phis (merge_bb); !gsi_end_p (gsi);