Contributed by Evgeny Stupachenko <evstupac@gmail.com>
- Copyright (C) 2015-2018 Free Software Foundation, Inc.
+ Copyright (C) 2015-2020 Free Software Foundation, Inc.
This file is part of GCC.
#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. */
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;
}
if (!idecl)
{
error_at (DECL_SOURCE_LOCATION (node->decl),
- "default target_clones attribute was not set");
+ "default %<target_clones%> attribute was not set");
return;
}
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)
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);
+ 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,
}
/* 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;
}
/* 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;
}
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;
}
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;
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;
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;
}