On 18/02/2025 12:11, Richard Sandiford wrote:
Alfie Richards <alfie.richa...@arm.com> writes:
This changes function version structures to maintain the default version
as the first declaration in the linked data structures by giving priority
to the set containing the default when constructing the structure.

This allows for removing logic for moving the default to the first
position which was duplicated across target specific code and enables
easier reasoning about function sets when checking for a default.

gcc/ChangeLog:

        * cgraph.cc (cgraph_node::record_function_versions): Update to
        implicitly keep default first.
        * config/aarch64/aarch64.cc (aarch64_get_function_versions_dispatcher):
        Remove reordering.
        * config/i386/i386-features.cc (ix86_get_function_versions_dispatcher):
        Remove reordering.
        * config/riscv/riscv.cc (riscv_get_function_versions_dispatcher):
        Remove reordering.
        * config/rs6000/rs6000.cc (rs6000_get_function_versions_dispatcher):
        Remove reordering.
---
  gcc/cgraph.cc                    | 27 ++++++++++++++++-----
  gcc/config/aarch64/aarch64.cc    | 37 +++++++---------------------
  gcc/config/i386/i386-features.cc | 33 ++++---------------------
  gcc/config/riscv/riscv.cc        | 41 +++++++-------------------------
  gcc/config/rs6000/rs6000.cc      | 35 +++++----------------------
  5 files changed, 49 insertions(+), 124 deletions(-)

diff --git a/gcc/cgraph.cc b/gcc/cgraph.cc
index d0b19ad850e..bf6b43d00db 100644
--- a/gcc/cgraph.cc
+++ b/gcc/cgraph.cc
@@ -247,7 +247,9 @@ cgraph_node::record_function_versions (tree decl1, tree 
decl2)
    decl1_v = decl1_node->function_version ();
    decl2_v = decl2_node->function_version ();
- if (decl1_v != NULL && decl2_v != NULL)
+  /* If the nodes are already linked, skip.  */
+  if ((decl1_v != NULL && (decl1_v->next || decl1_v->prev))
+      && (decl2_v != NULL && (decl2_v->next || decl2_v->prev)))
      return;
if (decl1_v == NULL)
@@ -256,18 +258,31 @@ cgraph_node::record_function_versions (tree decl1, tree 
decl2)
    if (decl2_v == NULL)
      decl2_v = decl2_node->insert_new_function_version ();
- /* Chain decl2_v and decl1_v. All semantically identical versions
-     will be chained together.  */
+  gcc_assert (decl1_v);
+  gcc_assert (decl2_v);
before = decl1_v;
    after = decl2_v;
+ /* Go to first after node. */
+  while (after->prev != NULL)
+    after = after->prev;
+
+  while (before->prev != NULL)
+    before = before->prev;
+
+  /* Potentially swap the nodes to maintain the default always being in the
+     first position.  */
+  if (before->next
+      ? !is_function_default_version (before->this_node->decl)
+      : is_function_default_version (after->this_node->decl))
+    std::swap (before, after);
+
+  /* Go to last node of before.  */
    while (before->next != NULL)
      before = before->next;
- while (after->prev != NULL)
-    after= after->prev;
-
+  /* Chain decl2_v and decl1_v.  */
I think this can be simplified to:

   before = decl1_v;
   after = decl2_v;

   /* Potentially swap the nodes to maintain the default always being in the
      first position.  */
   if (before->prev || before->next
       ? is_function_default_version (after->this_node->decl)
       : !is_function_default_version (before->this_node->decl))
     std::swap (before, after);

   while (before->next != NULL)
     before = before->next;
while (after->prev != NULL)
     after = after->prev;

That is, if one decl is linked (and so the other is not), we only want
to put the other decl first if it is the default.
I see your point here, which I think relies on the assumption that functions get
added to the structure one by one rather than in a fractal pattern.
This assumption is already used here subtly so that makes sense.

I added this logic to at least try make this work in a slightly more general case as to tell if a structure contains the default we should check the first element
of that structure, but it is unnecessary given that knowledge.

I would prefer to change this to make that more explicit and change this to be "add_decl_to_version_into" taking a cgraph_function_version_info for the existing structure and a decl for the version to add to make this explicit. Would that change work for you?
[...]
diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
index 9bf7713139f..e5aa99a4965 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -13726,7 +13726,6 @@ riscv_get_function_versions_dispatcher (void *decl)
    struct cgraph_node *node = NULL;
    struct cgraph_node *default_node = NULL;
    struct cgraph_function_version_info *node_v = NULL;
-  struct cgraph_function_version_info *first_v = NULL;
tree dispatch_decl = NULL; @@ -13743,41 +13742,19 @@ riscv_get_function_versions_dispatcher (void *decl)
    if (node_v->dispatcher_resolver != NULL)
      return node_v->dispatcher_resolver;
- /* Find the default version and make it the first node. */
-  first_v = node_v;
-  /* Go to the beginning of the chain.  */
-  while (first_v->prev != NULL)
-    first_v = first_v->prev;
-  default_version_info = first_v;
-
-  while (default_version_info != NULL)
-    {
-      struct riscv_feature_bits res;
-      int priority; /* Unused.  */
-      parse_features_for_version (default_version_info->this_node->decl,
-                                 res, priority);
-      if (res.length == 0)
-       break;
-      default_version_info = default_version_info->next;
-    }
+  /* The default node is always the beginning of the chain.  */
+  default_version_info = node_v;
+  while (default_version_info->prev)
+    default_version_info = default_version_info->prev;
+  default_node = default_version_info->this_node;
/* If there is no default node, just return NULL. */
-  if (default_version_info == NULL)
+  struct riscv_feature_bits res;
+  int priority; /* Unused.  */
+  parse_features_for_version (default_node->decl, res, priority);
+  if (res.length != 0)
      return NULL;
My comment from the previous review still stands:

   I expect you were trying to avoid editorialising, but: how about
   converting this to is_function_default_version at the same time?
   That seems more logically consistent with the target-independent code
   and wasn't an option open to the RISC-V code until patch 5.

   To put it another way, not being able to use is_function_default_version
   here would suggest that the "default-first" assumption doesn't in fact
   hold for RISC-V.

I.e. I think this can just be:

   if (!is_function_default_version (default_node->decl))
     return NULL;

as for the others.
Apologies for this, I meant to change this and must have missed it.
I agree with your reasoning.
OK for GCC 16 with those changes, if you agree with them.

Thanks,
Richard

Reply via email to