On Fri, Jul 17, 2026 at 4:36 PM Richard Biener <[email protected]> wrote:
> > I tried your idea, with some changes.
> >
> > 1. The returned op0 child isn't itself external in PR125800:
> >
> > current commutative node
> > `-- op0: internal child
> > `-- external operand
> >
> > So the `SLP_TREE_DEF_TYPE (child) == vect_external_def` check doesn't
> > catch it. I use the matches[] to detect the fallback and decide
> > whether to save child and retry swap.
>
> But isn't that good because we then pick the unswapped variant
> immediately?
> That was my idea to preserve the depth == 1 external build check at
> least.
both tests have the same shape:
P: current commutative node
`-- A: op0: internal child
`-- E: external operand
PR125800 wants the unswapped variant, while PR125567 wants the swapped
one. So checking SLP_TREE_DEF_TYPE (child) skip them both and keep
unswapped.
checking A's child doesn't work either, since it may be a normal
external input rather than one created by the failback path.
> > 2. besides saved_child, I also save tree_size, max_nunits, and the
> > swapped operand state, so a failed retry can be rolled back.
>
> Yes, of course.
>
> > all these changes make the code a little complicated and feel somewhat
> > repetitive. do you see a cleaner way?
>
> I suppose some bigger refactoring and C++ could help make it more
> readable.
>
> But as you also noticed the cache doesn't preserve matches[] as
> it "fails" when we then build from scalars but it records all
> success (from scalar build is success here), the whole idea
> to re-use the first child build is probably fragile :/ That said,
> the cache lookup leaves matches[] as-is (which is probably
> less than ideal), that said - we could populate the ->failed[]
> array explicitly when succeeding a from-scalar build and upon
> cache hit restore that if available, plus document this use,
> of course. I think that would make sense. And otherwise,
> on cache-hit, make sure to fill matches[] with true.
>
Right. Here is my new try.
diff --git a/gcc/tree-vect-slp.cc b/gcc/tree-vect-slp.cc
index 2250f6f74a1..132930e9af7 100644
--- a/gcc/tree-vect-slp.cc
+++ b/gcc/tree-vect-slp.cc
@@ -1873,12 +1873,6 @@ vect_slp_linearize_chain (vec_info *vinfo,
}
}
-/* Distance from the node currently being discovered to the closest upthread
- commutative operation whose operand-zero discovery may still be fixed by
- retrying with swapped operands, or -1U if there is none. */
-
-static unsigned least_upthread_swappable_op_distance = -1U;
-
static slp_tree
vect_build_slp_tree_2 (vec_info *vinfo, slp_tree node,
vec<stmt_vec_info> stmts, unsigned int group_size,
@@ -1886,6 +1880,30 @@ vect_build_slp_tree_2 (vec_info *vinfo, slp_tree node,
bool *matches, unsigned *limit, unsigned *tree_size,
scalar_stmts_to_slp_tree_map_t *bst_map);
+/* Restore fallback matches for NODE, or all true for a clean build. */
+
+static void
+vect_restore_slp_matches (slp_tree node, bool *matches, unsigned group_size)
+{
+ if (node->failed)
+ memcpy (matches, node->failed, sizeof (bool) * group_size);
+ else
+ for (unsigned i = 0; i < group_size; ++i)
+ matches[i] = true;
+}
+
+/* Record the fallback and first retryable MATCHES, if any. */
+
+static void
+vect_record_slp_fallback_matches (slp_tree node, bool *matches,
+ unsigned group_size)
+{
+ if (!node->failed)
+ node->failed = XCNEWVEC (bool, group_size);
+ if (matches[0] && !node->failed[0])
+ memcpy (node->failed, matches, sizeof (bool) * group_size);
+}
+
static slp_tree
vect_build_slp_tree (vec_info *vinfo,
vec<stmt_vec_info> stmts, unsigned int group_size,
@@ -1895,17 +1913,23 @@ vect_build_slp_tree (vec_info *vinfo,
{
if (slp_tree *leader = bst_map->get (stmts))
{
+ /* failed also stores matches for a successful scalar fallback, so use
+ def_type to tell whether the build failed. */
+ bool failed_p
+ = SLP_TREE_DEF_TYPE (*leader) == vect_uninitialized_def;
if (dump_enabled_p ())
dump_printf_loc (MSG_NOTE, vect_location, "re-using %sSLP tree %p\n",
- !(*leader)->failed ? "" : "failed ",
+ failed_p ? "failed " : "",
(void *) *leader);
- if (!(*leader)->failed)
+ if (!failed_p)
{
+ vect_restore_slp_matches (*leader, matches, group_size);
SLP_TREE_REF_COUNT (*leader)++;
vect_update_max_nunits (max_nunits, (*leader)->max_nunits);
stmts.release ();
return *leader;
}
+ gcc_assert ((*leader)->failed);
memcpy (matches, (*leader)->failed, sizeof (bool) * group_size);
return NULL;
}
@@ -1949,7 +1973,8 @@ vect_build_slp_tree (vec_info *vinfo,
as backedge destinations. */
SLP_TREE_SCALAR_STMTS (res) = vNULL;
SLP_TREE_DEF_TYPE (res) = vect_uninitialized_def;
- res->failed = XNEWVEC (bool, group_size);
+ if (!res->failed)
+ res->failed = XNEWVEC (bool, group_size);
if (flag_checking)
{
unsigned i;
@@ -1969,6 +1994,7 @@ vect_build_slp_tree (vec_info *vinfo,
gcc_assert (res_ == res);
res->max_nunits = this_max_nunits;
vect_update_max_nunits (max_nunits, this_max_nunits);
+ vect_restore_slp_matches (res, matches, group_size);
/* Keep a reference for the bst_map use. */
SLP_TREE_REF_COUNT (res)++;
}
@@ -2821,10 +2847,12 @@ out:
{
slp_tree child = nullptr;
unsigned int j;
- unsigned old_swap_distance;
bool can_swap;
bool can_swap_nonmatching;
bool *stmt_can_swap;
+ slp_tree saved_child = NULL;
+ unsigned pre_child_tree_size;
+ poly_uint64 pre_child_max_nunits;
/* We're skipping certain operands from processing, for example
outer loop reduction initial defs. */
@@ -3014,22 +3042,31 @@ out:
can_swap_nonmatching = false;
}
}
-
- old_swap_distance = least_upthread_swappable_op_distance;
- if (can_swap_nonmatching)
- least_upthread_swappable_op_distance = 1;
- else if (least_upthread_swappable_op_distance != -1U)
- least_upthread_swappable_op_distance++;
+ pre_child_tree_size = this_tree_size;
+ pre_child_max_nunits = this_max_nunits;
child = vect_build_slp_tree (vinfo, oprnd_info->def_stmts,
group_size, &this_max_nunits,
matches, limit,
&this_tree_size, bst_map);
- least_upthread_swappable_op_distance = old_swap_distance;
+
if (child != NULL)
{
- oprnd_info->def_stmts = vNULL;
- children.safe_push (child);
- continue;
+ if (is_a <bb_vec_info> (vinfo)
+ && can_swap_nonmatching
+ && matches[0])
+ for (j = 1; j < group_size; ++j)
+ if (!matches[j])
+ {
+ saved_child = child;
+ break;
+ }
+
+ if (!saved_child)
+ {
+ oprnd_info->def_stmts = vNULL;
+ children.safe_push (child);
+ continue;
+ }
}
/* If the SLP build for operand zero failed and operand zero
@@ -3060,6 +3097,18 @@ out:
}
while (j != group_size);
+ if (saved_child)
+ oprnd_info->def_stmts
+ = SLP_TREE_SCALAR_STMTS (saved_child).copy ();
+
+ bool saved_any_pattern[2]
+ = { oprnds_info[0]->any_pattern,
+ oprnds_info[1]->any_pattern };
+ unsigned retry_tree_size
+ = saved_child ? pre_child_tree_size : this_tree_size;
+ poly_uint64 retry_max_nunits
+ = saved_child ? pre_child_max_nunits : this_max_nunits;
+
/* Swap mismatched definition stmts. */
if (dump_enabled_p ())
dump_printf_loc (MSG_NOTE, vect_location,
@@ -3082,10 +3131,45 @@ out:
oprnds_info[0]->any_pattern = oprnds_info[1]->any_pattern = true;
/* And try again with scratch 'matches' ... */
bool *tem = XALLOCAVEC (bool, group_size);
- if ((child = vect_build_slp_tree (vinfo, oprnd_info->def_stmts,
- group_size, &this_max_nunits,
- tem, limit,
- &this_tree_size, bst_map)) != NULL)
+ child = vect_build_slp_tree (vinfo, oprnd_info->def_stmts,
+ group_size, &retry_max_nunits,
+ tem, limit,
+ &retry_tree_size, bst_map);
+ /* This is a speculative swap retry. Use child only for a clean
+ success; otherwise restore the operand state and saved_child. */
+ if (saved_child)
+ {
+ if (!child || child->failed)
+ {
+ /* A successful build owns the retry stmts, or released them
+ on a cache hit. Copy them back before undoing
the swap. */
+ if (child)
+ {
+ oprnd_info->def_stmts
+ = SLP_TREE_SCALAR_STMTS (child).copy ();
+ vect_free_slp_tree (child);
+ }
+ for (j = 0; j < group_size; ++j)
+ if (matches[j] == !swap_not_matching)
+ {
+ std::swap (oprnds_info[0]->def_stmts[j],
+ oprnds_info[1]->def_stmts[j]);
+ std::swap (oprnds_info[0]->ops[j],
+ oprnds_info[1]->ops[j]);
+ }
+ oprnds_info[0]->any_pattern = saved_any_pattern[0];
+ oprnds_info[1]->any_pattern = saved_any_pattern[1];
+ oprnd_info->def_stmts.release ();
+ children.safe_push (saved_child);
+ continue;
+ }
+
+ vect_free_slp_tree (saved_child);
+ }
+
+ this_tree_size = retry_tree_size;
+ this_max_nunits = retry_max_nunits;
+ if (child)
{
oprnd_info->def_stmts = vNULL;
children.safe_push (child);
@@ -3106,12 +3190,6 @@ fail:
/* ??? Rejecting patterns this way doesn't work. We'd have to
do extra work to cancel the pattern so the uses see the
scalar version. */
- /* Skip building vector operands from scalars while operand
- discovery may still be fixed by retrying with swapped operands. */
- && (least_upthread_swappable_op_distance != 1
- /* A first scalar stmt mismatch signals a fatal mismatch
- that the parent commutative retry cannot recover. */
- || !matches[0])
&& !is_pattern_stmt_p (stmt_info)
&& !oprnd_info->any_pattern)
{
@@ -3129,6 +3207,7 @@ fail:
if (dump_enabled_p ())
dump_printf_loc (MSG_NOTE, vect_location,
"Building vector operands from scalars\n");
+ vect_record_slp_fallback_matches (node, matches, group_size);
this_tree_size++;
child = vect_create_new_slp_node (oprnd_info->ops);
children.safe_push (child);
diff --git a/gcc/tree-vectorizer.h b/gcc/tree-vectorizer.h
index 0b646d6b29e..a21ca7e3ba1 100644
--- a/gcc/tree-vectorizer.h
+++ b/gcc/tree-vectorizer.h
@@ -391,9 +391,9 @@ struct _slp_tree {
template <class T>
T& get_data (T& else_) { return data ? *static_cast <T *> (data) : else_; }
- /* If not NULL this is a cached failed SLP discovery attempt with
- the lanes that failed during SLP discovery as 'false'. This is
- a copy of the matches array. */
+ /* def_type tells whether the cached build succeeded. On success, failed
+ is NULL for a clean build and otherwise holds scalar-fallback matches.
+ On failure, failed holds the matches of the failed build. */
bool *failed;
/* Allocate from slp_tree_pool. */
--
Regards,
Zhongyao