]> git.ipfire.org Git - thirdparty/gcc.git/blobdiff - gcc/multiple_target.c
Update copyright years.
[thirdparty/gcc.git] / gcc / multiple_target.c
index bdb5b3bf22815b5caa04b87e11a53a8f23d804b8..36fe58105dfb10f3bdc9e80943e67b4468bd4643 100644 (file)
@@ -2,7 +2,7 @@
 
    Contributed by Evgeny Stupachenko <evstupac@gmail.com>
 
-   Copyright (C) 2015-2017 Free Software Foundation, Inc.
+   Copyright (C) 2015-2020 Free Software Foundation, Inc.
 
 This file is part of GCC.
 
@@ -36,6 +36,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "pretty-print.h"
 #include "gimple-iterator.h"
 #include "gimple-walk.h"
+#include "tree-inline.h"
+#include "intl.h"
 
 /* Walker callback that replaces all FUNCTION_DECL of a function that's
    going to be versioned.  */
@@ -64,14 +66,53 @@ create_dispatcher_calls (struct cgraph_node *node)
 {
   ipa_ref *ref;
 
-  if (!DECL_FUNCTION_VERSIONED (node->decl))
+  if (!DECL_FUNCTION_VERSIONED (node->decl)
+      || !is_function_default_version (node->decl))
     return;
 
+  if (!targetm.has_ifunc_p ())
+    {
+      error_at (DECL_SOURCE_LOCATION (node->decl),
+               "the call requires %<ifunc%>, which is not"
+               " supported by this target");
+      return;
+    }
+  else if (!targetm.get_function_versions_dispatcher)
+    {
+      error_at (DECL_SOURCE_LOCATION (node->decl),
+               "target does not support function version dispatcher");
+      return;
+    }
+
+  tree idecl = targetm.get_function_versions_dispatcher (node->decl);
+  if (!idecl)
+    {
+      error_at (DECL_SOURCE_LOCATION (node->decl),
+               "default %<target_clones%> attribute was not set");
+      return;
+    }
+
+  cgraph_node *inode = cgraph_node::get (idecl);
+  gcc_assert (inode);
+  tree resolver_decl = targetm.generate_version_dispatcher_body (inode);
+
+  /* Update aliases.  */
+  inode->alias = true;
+  inode->alias_target = resolver_decl;
+  if (!inode->analyzed)
+    inode->resolve_alias (cgraph_node::get (resolver_decl));
+
   auto_vec<cgraph_edge *> edges_to_redirect;
-  auto_vec<ipa_ref *> references_to_redirect;
+  /* We need to capture the references by value rather than just pointers to them
+     and remove them right away, as removing them later would invalidate what
+     some other reference pointers point to.  */
+  auto_vec<ipa_ref> references_to_redirect;
 
-  for (unsigned i = 0; node->iterate_referring (i, ref); i++)
-    references_to_redirect.safe_push (ref);
+  while (node->iterate_referring (0, ref))
+    {
+      references_to_redirect.safe_push (*ref);
+      ref->remove_reference ();
+    }
 
   /* We need to remember NEXT_CALLER as it could be modified in the loop.  */
   for (cgraph_edge *e = node->callers; e ; e = e->next_caller)
@@ -79,38 +120,6 @@ create_dispatcher_calls (struct cgraph_node *node)
 
   if (!edges_to_redirect.is_empty () || !references_to_redirect.is_empty ())
     {
-      if (!targetm.has_ifunc_p ())
-       {
-         error_at (DECL_SOURCE_LOCATION (node->decl),
-                   "the call requires ifunc, which is not"
-                   " supported by this target");
-         return;
-       }
-      else if (!targetm.get_function_versions_dispatcher)
-       {
-         error_at (DECL_SOURCE_LOCATION (node->decl),
-                   "target does not support function version dispatcher");
-         return;
-       }
-
-      tree idecl = targetm.get_function_versions_dispatcher (node->decl);
-      if (!idecl)
-       {
-         error_at (DECL_SOURCE_LOCATION (node->decl),
-                   "default target_clones attribute was not set");
-         return;
-       }
-
-      cgraph_node *inode = cgraph_node::get (idecl);
-      gcc_assert (inode);
-      tree resolver_decl = targetm.generate_version_dispatcher_body (inode);
-
-      /* Update aliases.  */
-      inode->alias = true;
-      inode->alias_target = resolver_decl;
-      if (!inode->analyzed)
-       inode->resolve_alias (cgraph_node::get (resolver_decl));
-
       /* Redirect edges.  */
       unsigned i;
       cgraph_edge *e;
@@ -141,6 +150,16 @@ create_dispatcher_calls (struct cgraph_node *node)
                  if (ref->referring->decl != resolver_decl)
                    walk_gimple_stmt (&it, NULL, replace_function_decl, &wi);
                }
+
+             symtab_node *source = ref->referring;
+             source->create_reference (inode, IPA_REF_ADDR);
+           }
+         else if (ref->use == IPA_REF_ALIAS)
+           {
+             symtab_node *source = ref->referring;
+             source->create_reference (inode, IPA_REF_ALIAS);
+             if (inode->get_comdat_group ())
+               source->add_to_same_comdat_group (inode);
            }
          else
            gcc_unreachable ();
@@ -148,8 +167,24 @@ create_dispatcher_calls (struct cgraph_node *node)
     }
 
   symtab->change_decl_assembler_name (node->decl,
-                                     clone_function_name (node->decl,
-                                                          "default"));
+                                     clone_function_name_numbered (
+                                         node->decl, "default"));
+
+  /* FIXME: copy of cgraph_node::make_local that should be cleaned up
+           in next stage1.  */
+  node->make_decl_local ();
+  node->set_section (NULL);
+  node->set_comdat_group (NULL);
+  node->externally_visible = false;
+  node->forced_by_abi = false;
+  node->set_section (NULL);
+  node->unique_name = ((node->resolution == LDPR_PREVAILING_DEF_IRONLY
+                       || node->resolution == LDPR_PREVAILING_DEF_IRONLY_EXP)
+                      && !flag_incremental_link);
+  node->resolution = LDPR_PREVAILING_DEF_IRONLY;
+
+  DECL_ARTIFICIAL (node->decl) = 1;
+  node->force_output = true;
 }
 
 /* Return length of attribute names string,
@@ -201,26 +236,34 @@ get_attr_str (tree arglist, char *attr_str)
 }
 
 /* Return number of attributes separated by comma and put them into ARGS.
-   If there is no DEFAULT attribute return -1.  */
+   If there is no DEFAULT attribute return -1.
+   If there is an empty string in attribute return -2.
+   If there are multiple DEFAULT attributes return -3.
+   */
 
 static int
-separate_attrs (char *attr_str, char **attrs)
+separate_attrs (char *attr_str, char **attrs, int attrnum)
 {
   int i = 0;
-  bool has_default = false;
+  int default_count = 0;
 
   for (char *attr = strtok (attr_str, ",");
        attr != NULL; attr = strtok (NULL, ","))
     {
       if (strcmp (attr, "default") == 0)
        {
-         has_default = true;
+         default_count++;
          continue;
        }
       attrs[i++] = attr;
     }
-  if (!has_default)
+  if (default_count == 0)
     return -1;
+  else if (default_count > 1)
+    return -3;
+  else if (i + default_count < attrnum)
+    return -2;
+
   return i;
 }
 
@@ -260,26 +303,29 @@ create_new_asm_name (char *old_asm_name, char *new_asm_name)
 /*  Creates target clone of NODE.  */
 
 static cgraph_node *
-create_target_clone (cgraph_node *node, bool definition, char *name)
+create_target_clone (cgraph_node *node, bool definition, char *name,
+                    tree attributes)
 {
   cgraph_node *new_node;
 
   if (definition)
     {
       new_node = node->create_version_clone_with_body (vNULL, NULL,
-                                                      NULL, false,
-                                                      NULL, NULL,
-                                                      name);
+                                                      NULL, NULL,
+                                                      NULL, name, attributes);
+      if (new_node == NULL)
+       return NULL;
       new_node->force_output = true;
     }
   else
     {
       tree new_decl = copy_node (node->decl);
       new_node = cgraph_node::get_create (new_decl);
+      DECL_ATTRIBUTES (new_decl) = attributes;
       /* Generate a new name for the new version.  */
       symtab->change_decl_assembler_name (new_node->decl,
-                                         clone_function_name (node->decl,
-                                                              name));
+                                         clone_function_name_numbered (
+                                             node->decl, name));
     }
   return new_node;
 }
@@ -305,8 +351,27 @@ expand_target_clones (struct cgraph_node *node, bool definition)
   if (attr_len == -1)
     {
       warning_at (DECL_SOURCE_LOCATION (node->decl),
-                 0,
-                 "single target_clones attribute is ignored");
+                 0, "single %<target_clones%> attribute is ignored");
+      return false;
+    }
+
+  if (node->definition
+      && (node->alias || !tree_versionable_function_p (node->decl)))
+    {
+      auto_diagnostic_group d;
+      error_at (DECL_SOURCE_LOCATION (node->decl),
+               "clones for %<target_clones%> attribute cannot be created");
+      const char *reason = NULL;
+      if (lookup_attribute ("noclone", DECL_ATTRIBUTES (node->decl)))
+       reason = G_("function %q+F can never be copied "
+                   "because it has %<noclone%> attribute");
+      else if (node->alias)
+       reason
+         = "%<target_clones%> cannot be combined with %<alias%> attribute";
+      else
+       reason = copy_forbidden (DECL_STRUCT_FUNCTION (node->decl));
+      if (reason)
+       inform (DECL_SOURCE_LOCATION (node->decl), reason, node->decl);
       return false;
     }
 
@@ -314,11 +379,27 @@ expand_target_clones (struct cgraph_node *node, bool definition)
   int attrnum = get_attr_str (arglist, attr_str);
   char **attrs = XNEWVEC (char *, attrnum);
 
-  attrnum = separate_attrs (attr_str, attrs);
-  if (attrnum == -1)
+  attrnum = separate_attrs (attr_str, attrs, attrnum);
+  switch (attrnum)
     {
+    case -1:
       error_at (DECL_SOURCE_LOCATION (node->decl),
-               "default target was not set");
+               "%<default%> target was not set");
+      break;
+    case -2:
+      error_at (DECL_SOURCE_LOCATION (node->decl),
+               "an empty string cannot be in %<target_clones%> attribute");
+      break;
+    case -3:
+      error_at (DECL_SOURCE_LOCATION (node->decl),
+               "multiple %<default%> targets were set");
+      break;
+    default:
+      break;
+    }
+
+  if (attrnum < 0)
+    {
       XDELETEVEC (attrs);
       XDELETEVEC (attr_str);
       return false;
@@ -341,22 +422,16 @@ expand_target_clones (struct cgraph_node *node, bool definition)
 
       create_new_asm_name (attr, suffix);
       /* Create new target clone.  */
-      cgraph_node *new_node = create_target_clone (node, definition, suffix);
-      new_node->local.local = false;
-      XDELETEVEC (suffix);
-
-      /* Set new attribute for the clone.  */
       tree attributes = make_attribute ("target", attr,
-                                       DECL_ATTRIBUTES (new_node->decl));
-      DECL_ATTRIBUTES (new_node->decl) = attributes;
-      location_t saved_loc = input_location;
-      input_location = DECL_SOURCE_LOCATION (node->decl);
-      if (!targetm.target_option.valid_attribute_p (new_node->decl, NULL,
-                                                   TREE_VALUE (attributes),
-                                                   0))
+                                       DECL_ATTRIBUTES (node->decl));
+
+      cgraph_node *new_node = create_target_clone (node, definition, suffix,
+                                                  attributes);
+      if (new_node == NULL)
        return false;
+      new_node->local = false;
+      XDELETEVEC (suffix);
 
-      input_location = saved_loc;
       decl2_v = new_node->function_version ();
       if (decl2_v != NULL)
         continue;
@@ -382,28 +457,73 @@ expand_target_clones (struct cgraph_node *node, bool definition)
   tree attributes = make_attribute ("target", "default",
                                    DECL_ATTRIBUTES (node->decl));
   DECL_ATTRIBUTES (node->decl) = attributes;
-  node->local.local = false;
-  location_t saved_loc = input_location;
-  input_location = DECL_SOURCE_LOCATION (node->decl);
-  bool ret
-    = targetm.target_option.valid_attribute_p (node->decl, NULL,
-                                              TREE_VALUE (attributes), 0);
-  input_location = saved_loc;
-  return ret;
+  node->local = false;
+  return true;
+}
+
+/* When NODE is a target clone, consider all callees and redirect
+   to a clone with equal target attributes.  That prevents multiple
+   multi-versioning dispatches and a call-chain can be optimized.  */
+
+static void
+redirect_to_specific_clone (cgraph_node *node)
+{
+  cgraph_function_version_info *fv = node->function_version ();
+  if (fv == NULL)
+    return;
+
+  tree attr_target = lookup_attribute ("target", DECL_ATTRIBUTES (node->decl));
+  if (attr_target == NULL_TREE)
+    return;
+
+  /* We need to remember NEXT_CALLER as it could be modified in the loop.  */
+  for (cgraph_edge *e = node->callees; e ; e = e->next_callee)
+    {
+      cgraph_function_version_info *fv2 = e->callee->function_version ();
+      if (!fv2)
+       continue;
+
+      tree attr_target2 = lookup_attribute ("target",
+                                           DECL_ATTRIBUTES (e->callee->decl));
+
+      /* Function is not calling proper target clone.  */
+      if (!attribute_list_equal (attr_target, attr_target2))
+       {
+         while (fv2->prev != NULL)
+           fv2 = fv2->prev;
+
+         /* Try to find a clone with equal target attribute.  */
+         for (; fv2 != NULL; fv2 = fv2->next)
+           {
+             cgraph_node *callee = fv2->this_node;
+             attr_target2 = lookup_attribute ("target",
+                                              DECL_ATTRIBUTES (callee->decl));
+             if (attribute_list_equal (attr_target, attr_target2))
+               {
+                 e->redirect_callee (callee);
+                 e->redirect_call_stmt_to_callee ();
+                 break;
+               }
+           }
+       }
+    }
 }
 
 static unsigned int
 ipa_target_clone (void)
 {
   struct cgraph_node *node;
+  auto_vec<cgraph_node *> to_dispatch;
 
-  bool target_clone_pass = false;
   FOR_EACH_FUNCTION (node)
-    target_clone_pass |= expand_target_clones (node, node->definition);
+    if (expand_target_clones (node, node->definition))
+      to_dispatch.safe_push (node);
 
-  if (target_clone_pass)
-    FOR_EACH_FUNCTION (node)
-      create_dispatcher_calls (node);
+  for (unsigned i = 0; i < to_dispatch.length (); i++)
+    create_dispatcher_calls (to_dispatch[i]);
+
+  FOR_EACH_FUNCTION (node)
+    redirect_to_specific_clone (node);
 
   return 0;
 }