]> git.ipfire.org Git - thirdparty/gcc.git/blobdiff - gcc/multiple_target.c
PR fortran/95090 - ICE: identifier overflow
[thirdparty/gcc.git] / gcc / multiple_target.c
index 0d7cc3a2939193f5c1a23f02f01030059d4e5f98..c1cfe8ff978192b73c704a4f5a380f6392addf9a 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.  */
@@ -71,7 +73,7 @@ create_dispatcher_calls (struct cgraph_node *node)
   if (!targetm.has_ifunc_p ())
     {
       error_at (DECL_SOURCE_LOCATION (node->decl),
-               "the call requires ifunc, which is not"
+               "the call requires %<ifunc%>, which is not"
                " supported by this target");
       return;
     }
@@ -86,7 +88,7 @@ create_dispatcher_calls (struct cgraph_node *node)
   if (!idecl)
     {
       error_at (DECL_SOURCE_LOCATION (node->decl),
-               "default target_clones attribute was not set");
+               "default %<target_clones%> attribute was not set");
       return;
     }
 
@@ -101,10 +103,16 @@ create_dispatcher_calls (struct cgraph_node *node)
     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)
@@ -118,7 +126,7 @@ create_dispatcher_calls (struct cgraph_node *node)
       FOR_EACH_VEC_ELT (edges_to_redirect, i, e)
        {
          e->redirect_callee (inode);
-         e->redirect_call_stmt_to_callee ();
+         cgraph_edge::redirect_call_stmt_to_callee (e);
        }
 
       /* Redirect references.  */
@@ -142,16 +150,37 @@ 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 ();
        }
     }
 
-  TREE_PUBLIC (node->decl) = 0;
   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);
+
+  DECL_ARTIFICIAL (node->decl) = 1;
+  node->force_output = true;
 }
 
 /* Return length of attribute names string,
@@ -203,26 +232,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;
 }
 
@@ -262,26 +299,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;
 }
@@ -307,8 +347,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;
     }
 
@@ -316,11 +375,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;
@@ -343,22 +418,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;
@@ -384,28 +453,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);
+                 cgraph_edge::redirect_call_stmt_to_callee (e);
+                 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;
 }