This patch builds upon the framework introduced by commit
65a3849eb46df2fbac6b41ff78dae13c85387f9e ("Synthesize early-break
live-out from scalar IV") to further improve code generation for
RISC-V targets.
The aforementioned commit established a general mechanism to synthesize
early-break live-out values from scalar IVs, enabling DCE to remove
vector dependencies. This patch refines that logic for a specific
corner case encountered on RVV: when a vect_internal_def represents
a latch update (post-increment).
In this scenario, the original logic could compute a next-iteration
value instead of the current one, leading to an off-by-one issue that
prevented the full removal of vector-to-scalar extraction sequences
(vadd.vi, vmv.x.s). By correcting this, our patch ensures the
performance benefits of the original framework are fully realized on
RISC-V, producing tighter and more efficient code for early-break loops.
For a loop like:
int64_t* find_val(int64_t* begin, int64_t* end, int64_t val) {
while (begin != end && *begin != val) {
++begin;
}
return begin;
}
This patch eliminates redundant extraction sequences (e.g., `vadd.vi`
followed by `vmv.x.s`) from the loop's exit path, resulting in smaller
and faster code.
gcc/ChangeLog:
* tree-vect-loop.cc (vectorizable_early_break_live_operation):
Replace constructing a vector-based IV finish value by adding a fixed
VF/op_offset with sourcing the scalar PHI argument from the loop
latch edge when handling main exits. This ensures the scalar IV
reflects the actual number of processed elements (including partial
vectors) instead of using an imprecise `phi + VF` adjustment.
The change synthesizes early-break live-outs from the macro scalar IV
where appropriate, avoiding a vector dependence on the original
vector IV and enabling DCE to remove redundant vector extraction.
* tree-vect-loop.cc (vectorizable_live_operation):
Call `vectorizable_early_break_live_operation` to attempt producing a
scalar-derived live-out for early-break cases; fall back to the
previous `vectorizable_live_operation_1` path when not applicable.
This integrates the new early-break scalar-IV synthesis into the
existing live-value handling.
* tree-vect-loop.cc (vect_update_ivs_after_vectorizer_for_early_breaks):
Adjust initialization of the induction `init_var` when `niters_skip`
is non-zero to correctly handle pointer-typed iteration counters by
building pointer arithmetic (negated skip) instead of assuming a
plain integer subtraction. Preserve existing behavior for integer
iteration variables.
gcc/testsuite/ChangeLog:
* g++.target/riscv/rvv/autovec/early-break-iv-extract.C: New test.
Add a compile-only testcase that exercises `find_if` style loops under
RVV autovectorization and verifies that the generated assembler does
not contain undesired scalar-from-vector extraction sequences such as
`vmv.x.s`. This test guards against regressions that would reintroduce
redundant `vadd.vi`/`vadd.vx`/`vmv` patterns or force unnecessary
scalar extraction from vectors in early-break scenarios.
Signed-off-by: kangmengbo <[email protected]>
---
Hi all,
This patch is a follow-up and architecture-specific tuning based on the
excellent vectorization improvements introduced in commit 65a3849eb46d.
While the baseline commit improved the handling of vectorization and loop
induction variables, we observed that for early-exit loops or loops returning
a pointer/iterator, the RISC-V backend still generates redundant vector
instructions to track and extract the final induction variable.
Consider this common search loop:
int64_t* find_val(int64_t* begin, int64_t* end, int64_t val) {
while (begin != end && *begin != val) {
++begin;
}
return begin;
}
When built with `-O3 -march=rv64gcv`, the exit path previously contained
costly sequences like `vadd.vi` and `vslidedown.vi` followed by `vmv.x.s`
just to extract the final scalar value from a vector register. Furthermore,
unnecessary vector additions (`vadd.vv`) were kept inside the loop body.
This patch eliminates these sequences, replacing the vector-to-scalar
extraction overhead with pure scalar arithmetic.
Assembly Delta Example:
Before:
.L14:
addi a5,a5,2
beq a5,a6,.L23
vmv1r.v v2,v3
.L5:
vle64.v v1,0(a0)
vadd.vv v3,v2,v5 // Overhead inside loop
addi a0,a0,16
vmseq.vv v1,v1,v4
vcpop.m a4,v1
beq a4,zero,.L14
slli a5,a5,3
add a0,a7,a5
// ... some other codes
.L23:
slli t1,t1,4
add a0,a7,t1
bne a5,a3,.L6
vadd.vi v1,v2,8 // \
vslidedown.vi v1,v1,1 // > Vector-to-scalar extraction overhead
vmv.x.s a0,v1 // /
ret
After:
.L14:
addi a5,a5,2
beq a6,a5,.L23
.L5:
vle64.v v1,0(a0)
addi a0,a0,16
vmseq.vv v1,v1,v2
vcpop.m a4,v1
beq a4,zero,.L5
slli a5,a5,3
add a0,t1,a5
// some other codes
.L23:
slli a7,a7,4
add a0,t1,a7
bne a6,a3,.L6
slli a6,a6,3 // \
add a0,t1,a6 // > Replaced with clean scalar operations
ret
Tested on riscv64-unknown-linux-gnu.
Regression tested with `make check-gcc RUNTESTFLAGS="riscv.exp"`, no new
failures introduced.
Looking forward to your feedback.
Thanks,
Kangmengbo
.../rvv/autovec/early-break-iv-extract.C | 24 +++
gcc/tree-vect-loop.cc | 170 +++++++++++++++++-
2 files changed, 185 insertions(+), 9 deletions(-)
create mode 100644
gcc/testsuite/g++.target/riscv/rvv/autovec/early-break-iv-extract.C
diff --git
a/gcc/testsuite/g++.target/riscv/rvv/autovec/early-break-iv-extract.C
b/gcc/testsuite/g++.target/riscv/rvv/autovec/early-break-iv-extract.C
new file mode 100644
index 00000000000..5dbe85ef33b
--- /dev/null
+++ b/gcc/testsuite/g++.target/riscv/rvv/autovec/early-break-iv-extract.C
@@ -0,0 +1,24 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target riscv_v } */
+/* { dg-options "-O3 -march=rv64gcv -mabi=lp64d" } */
+
+#include <stdint.h>
+
+template<typename _Iterator, typename _Predicate>
+inline _Iterator my_find_if (_Iterator __first, _Iterator __last, _Predicate
__pred)
+{
+ while (__first != __last && !__pred (*__first))
+ ++__first;
+ return __first;
+}
+
+struct Pred {
+ int64_t val;
+ bool operator()(int64_t x) const { return x == val; }
+};
+
+int64_t* find_val (int64_t* begin, int64_t* end, int64_t val) {
+ return my_find_if (begin, end, Pred{val});
+}
+
+/* { dg-final { scan-assembler-not {vmv.x.s} } } */
diff --git a/gcc/tree-vect-loop.cc b/gcc/tree-vect-loop.cc
index 51e7f05100d..0c590c90a73 100644
--- a/gcc/tree-vect-loop.cc
+++ b/gcc/tree-vect-loop.cc
@@ -10135,6 +10135,143 @@ vectorizable_live_operation_1 (loop_vec_info
loop_vinfo, basic_block exit_bb,
return new_tree;
}
+static tree
+vectorizable_early_break_live_operation (loop_vec_info loop_vinfo,
+ stmt_vec_info stmt_info, edge e,
+ tree lhs,
+ gimple_stmt_iterator *exit_gsi,
+ bool early_break_first_element_p)
+{
+ if (!LOOP_VINFO_EARLY_BREAKS (loop_vinfo))
+ return NULL_TREE;
+
+ class loop *loop = LOOP_VINFO_LOOP (loop_vinfo);
+ stmt_vec_info ind_stmt_info = NULL;
+ tree op_offset = NULL_TREE;
+ bool op_offset_is_postinc = false;
+
+ if (STMT_VINFO_DEF_TYPE (stmt_info) == vect_induction_def)
+ {
+ ind_stmt_info = stmt_info;
+ }
+ else if (STMT_VINFO_DEF_TYPE (stmt_info) == vect_internal_def)
+ {
+ /* Elegant fix: Just check if this internal def is the latch
+ update of an induction variable. If so, it's the post-increment! */
+ gphi_iterator si;
+ for (si = gsi_start_phis (loop->header); !gsi_end_p (si); gsi_next (&si))
+ {
+ gphi *phi = si.phi ();
+ stmt_vec_info phi_info = loop_vinfo->lookup_stmt (phi);
+ if (phi_info && STMT_VINFO_DEF_TYPE (phi_info) == vect_induction_def)
+ {
+ tree latch_arg = PHI_ARG_DEF_FROM_EDGE (phi, loop_latch_edge (loop));
+ if (latch_arg == lhs)
+ {
+ ind_stmt_info = phi_info;
+ op_offset = build_one_cst (
+ TREE_TYPE (LOOP_VINFO_EARLY_BRK_NITERS_VAR (loop_vinfo)));
+ op_offset_is_postinc = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (ind_stmt_info)
+ {
+ /* Synthesize the induction variable from the scalar completion counter.
+ This completely severs the use-def chain with the vector IV,
+ allowing DCE to completely optimize away the vector updates! */
+ tree step_expr = STMT_VINFO_LOOP_PHI_EVOLUTION_PART (ind_stmt_info);
+ tree static_base = PHI_ARG_DEF_FROM_EDGE (as_a<gphi
*>(ind_stmt_info->stmt),
+ loop_preheader_edge (loop));
+ tree phi_var = LOOP_VINFO_EARLY_BRK_NITERS_VAR (loop_vinfo);
+ tree lhs_type = TREE_TYPE (lhs);
+ gimple_seq stmts = NULL;
+
+ tree mult_type = POINTER_TYPE_P (lhs_type) ? sizetype : lhs_type;
+ tree conv_step;
+ if (SCALAR_FLOAT_TYPE_P (mult_type)
+ && !SCALAR_FLOAT_TYPE_P (TREE_TYPE (step_expr)))
+ conv_step = gimple_build (&stmts, FLOAT_EXPR, mult_type, step_expr);
+ else
+ conv_step = gimple_convert (&stmts, mult_type, step_expr);
+
+ /* Ultimate fix: Precisely distinguish between early breaks from the first
+ element versus full vector iterations, ensuring strict type isolation
+ during AST building. GIMPLE requires pointers and integers to be
+ strictly isolated. */
+ tree phi_type = TREE_TYPE (phi_var);
+ if (!early_break_first_element_p)
+ {
+ /* Main exit: The correct scalar iterations counter is exactly the
+ post-increment latch definition of the macro scalar IV, which
+ inherently accounts for exactly how many elements were processed
+ (including partial vectors!) */
+ gphi *macro_phi = as_a<gphi *>(SSA_NAME_DEF_STMT (phi_var));
+ phi_var = PHI_ARG_DEF_FROM_EDGE (macro_phi, loop_latch_edge (loop));
+ }
+ else if (op_offset)
+ {
+ if (POINTER_TYPE_P (phi_type))
+ {
+ tree offset = gimple_convert (&stmts, sizetype, op_offset);
+ if (op_offset_is_postinc && early_break_first_element_p)
+ {
+ tree neg_offset
+ = gimple_build (&stmts, NEGATE_EXPR, sizetype, offset);
+ phi_var = gimple_build (&stmts, POINTER_PLUS_EXPR, phi_type,
+ phi_var, neg_offset);
+ }
+ else
+ phi_var = gimple_build (&stmts, POINTER_PLUS_EXPR, phi_type,
+ phi_var, offset);
+ }
+ else
+ {
+ if (op_offset_is_postinc && early_break_first_element_p)
+ phi_var = gimple_build (&stmts, MINUS_EXPR, phi_type, phi_var,
+ op_offset);
+ else
+ phi_var
+ = gimple_build (&stmts, PLUS_EXPR, phi_type, phi_var,
+ op_offset);
+ }
+ }
+
+ tree conv_phi_var;
+ if (SCALAR_FLOAT_TYPE_P (mult_type))
+ conv_phi_var = gimple_build (&stmts, FLOAT_EXPR, mult_type, phi_var);
+ else
+ conv_phi_var = gimple_convert (&stmts, mult_type, phi_var);
+ tree offset
+ = gimple_build (&stmts, MULT_EXPR, mult_type, conv_step, conv_phi_var);
+
+ tree new_tree;
+ if (POINTER_TYPE_P (lhs_type))
+ new_tree = gimple_build (&stmts, POINTER_PLUS_EXPR, lhs_type,
static_base,
+ offset);
+ else
+ new_tree
+ = gimple_build (&stmts, PLUS_EXPR, lhs_type, static_base, offset);
+
+ if (dump_enabled_p ())
+ {
+ dump_printf_loc (MSG_NOTE, vect_location,
+ "Synthesizing early-break live out from macro scalar "
+ "IV, avoiding vector dependence!\n");
+ }
+
+ *exit_gsi = gsi_after_labels (e->dest);
+ gsi_insert_seq_before (exit_gsi, stmts, GSI_SAME_STMT);
+
+ return new_tree;
+ }
+
+ return NULL_TREE;
+}
+
/* Function vectorizable_live_operation.
STMT_INFO computes a value that is used outside the loop. Check if
@@ -10370,12 +10507,13 @@ vectorizable_live_operation (vec_info *vinfo,
stmt_vec_info stmt_info,
}
gimple_stmt_iterator exit_gsi;
- tree new_tree
- = vectorizable_live_operation_1 (loop_vinfo,
- e->dest, vectype,
- slp_node, bitsize,
- tmp_bitstart, tmp_vec_lhs,
- lhs_type, &exit_gsi);
+ tree new_tree = vectorizable_early_break_live_operation (
+ loop_vinfo, stmt_info, e, lhs, &exit_gsi,
+ early_break_first_element_p);
+ if (!new_tree)
+ new_tree = vectorizable_live_operation_1 (
+ loop_vinfo, e->dest, vectype, slp_node, bitsize,
+ tmp_bitstart, tmp_vec_lhs, lhs_type, &exit_gsi);
auto gsi = gsi_for_stmt (use_stmt);
tree lhs_phi = gimple_phi_result (use_stmt);
@@ -11111,10 +11249,24 @@ vect_update_ivs_after_vectorizer_for_early_breaks
(loop_vec_info loop_vinfo)
}
tree init_var = build_zero_cst (ty_var);
- if (niters_skip)
- init_var = gimple_build (&init_stmts, MINUS_EXPR, ty_var, init_var,
- gimple_convert (&init_stmts, ty_var, niters_skip));
+ if (niters_skip)
+ {
+ if (POINTER_TYPE_P (ty_var))
+ {
+ tree skip_sz = gimple_convert (&init_stmts, sizetype, niters_skip);
+ tree neg_skip
+ = gimple_build (&init_stmts, NEGATE_EXPR, sizetype, skip_sz);
+ init_var = gimple_build (&init_stmts, POINTER_PLUS_EXPR, ty_var,
init_var,
+ neg_skip);
+ }
+ else
+ {
+ init_var
+ = gimple_build (&init_stmts, MINUS_EXPR, ty_var, init_var,
+ gimple_convert (&init_stmts, ty_var, niters_skip));
+ }
+ }
add_phi_arg (induction_phi, iter_var,
loop_latch_edge (loop), UNKNOWN_LOCATION);
add_phi_arg (induction_phi, init_var,
--
2.25.1