[committed,v4,4/5] Add support for target_version attribute

Message ID 12b289bb-ab28-a93c-5ebc-83bfa9572871@e124511.cambridge.arm.com
State New
Headers
Series None |

Commit Message

Andrew Carlotti Dec. 16, 2023, 12:45 a.m. UTC
  This patch adds support for the "target_version" attribute to the middle
end and the C++ frontend, which will be used to implement function
multiversioning in the aarch64 backend.

On targets that don't use the "target" attribute for multiversioning,
there is no conflict between the "target" and "target_clones"
attributes.  This patch therefore makes the mutual exclusion in
C-family, D and Ada conditonal upon the value of the
expanded_clones_attribute target hook.

The "target_version" attribute is only added to C++ in this patch,
because this is currently the only frontend which supports
multiversioning using the "target" attribute.  Support for the
"target_version" attribute will be extended to C at a later date.

Targets that currently use the "target" attribute for function
multiversioning (i.e. i386 and rs6000) are not affected by this patch.

Committed as approved with adjustments to comments in c-attribs.

gcc/ChangeLog:

	* attribs.cc (decl_attributes): Pass attribute name to target.
	(is_function_default_version): Update comment to specify
	incompatibility with target_version attributes.
	* cgraphclones.cc (cgraph_node::create_version_clone_with_body):
	Call valid_version_attribute_p for target_version attributes.
	* defaults.h (TARGET_HAS_FMV_TARGET_ATTRIBUTE): New macro.
	* target.def (valid_version_attribute_p): New hook.
	* doc/tm.texi.in: Add new hook.
	* doc/tm.texi: Regenerate.
	* multiple_target.cc (create_dispatcher_calls): Remove redundant
	is_function_default_version check.
	(expand_target_clones): Use target macro to pick attribute name.
	* targhooks.cc (default_target_option_valid_version_attribute_p):
	New.
	* targhooks.h (default_target_option_valid_version_attribute_p):
	New.
	* tree.h (DECL_FUNCTION_VERSIONED): Update comment to include
	target_version attributes.

gcc/c-family/ChangeLog:

	* c-attribs.cc (attr_target_exclusions): Make
	target/target_clones exclusion target-dependent.
	(attr_target_clones_exclusions): Ditto, and add target_version.
	(attr_target_version_exclusions): New.
	(c_common_attribute_table): Add target_version.
	(handle_target_version_attribute): New.
	(handle_target_attribute): Amend comment.
	(handle_target_clones_attribute): Ditto.

gcc/ada/ChangeLog:

	* gcc-interface/utils.cc (attr_target_exclusions): Make
	target/target_clones exclusion target-dependent.
	(attr_target_clones_exclusions): Ditto.

gcc/d/ChangeLog:

	* d-attribs.cc (attr_target_exclusions): Make
	target/target_clones exclusion target-dependent.
	(attr_target_clones_exclusions): Ditto.

gcc/cp/ChangeLog:

	* decl2.cc (check_classfn): Update comment to include
	target_version attributes.
  

Patch

diff --git a/gcc/ada/gcc-interface/utils.cc b/gcc/ada/gcc-interface/utils.cc
index 3eabbec6bd34116910a0589b4ebf269b916cc607..17f6afd687d1dbd7648d52d86417414b04c0d896 100644
--- a/gcc/ada/gcc-interface/utils.cc
+++ b/gcc/ada/gcc-interface/utils.cc
@@ -146,14 +146,16 @@  static const struct attribute_spec::exclusions attr_noinline_exclusions[] =
 
 static const struct attribute_spec::exclusions attr_target_exclusions[] =
 {
-  { "target_clones", true, true, true },
+  { "target_clones", TARGET_HAS_FMV_TARGET_ATTRIBUTE,
+    TARGET_HAS_FMV_TARGET_ATTRIBUTE, TARGET_HAS_FMV_TARGET_ATTRIBUTE },
   { NULL, false, false, false },
 };
 
 static const struct attribute_spec::exclusions attr_target_clones_exclusions[] =
 {
   { "always_inline", true, true, true },
-  { "target", true, true, true },
+  { "target", TARGET_HAS_FMV_TARGET_ATTRIBUTE, TARGET_HAS_FMV_TARGET_ATTRIBUTE,
+    TARGET_HAS_FMV_TARGET_ATTRIBUTE },
   { NULL, false, false, false },
 };
 
diff --git a/gcc/attribs.cc b/gcc/attribs.cc
index 4e313d38f0f0608991c3267f55f43e3f0dd9d74a..0ca2779788569b7a02a79eab4db558df112aff87 100644
--- a/gcc/attribs.cc
+++ b/gcc/attribs.cc
@@ -675,7 +675,8 @@  decl_attributes (tree *node, tree attributes, int flags,
      options to the attribute((target(...))) list.  */
   if (TREE_CODE (*node) == FUNCTION_DECL
       && current_target_pragma
-      && targetm.target_option.valid_attribute_p (*node, NULL_TREE,
+      && targetm.target_option.valid_attribute_p (*node,
+						  get_identifier ("target"),
 						  current_target_pragma, 0))
     {
       tree cur_attr = lookup_attribute ("target", attributes);
@@ -1276,8 +1277,9 @@  make_dispatcher_decl (const tree decl)
   return func_decl;  
 }
 
-/* Returns true if decl is multi-versioned and DECL is the default function,
-   that is it is not tagged with target specific optimization.  */
+/* Returns true if DECL is multi-versioned using the target attribute, and this
+   is the default version.  This function can only be used for targets that do
+   not support the "target_version" attribute.  */
 
 bool
 is_function_default_version (const tree decl)
diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc
index 9638848591fc82d3f7116cadf38b11237206ac0b..169c96a1397fce15365c1630767331392208f758 100644
--- a/gcc/c-family/c-attribs.cc
+++ b/gcc/c-family/c-attribs.cc
@@ -151,6 +151,7 @@  static tree handle_alloc_align_attribute (tree *, tree, tree, int, bool *);
 static tree handle_assume_aligned_attribute (tree *, tree, tree, int, bool *);
 static tree handle_assume_attribute (tree *, tree, tree, int, bool *);
 static tree handle_target_attribute (tree *, tree, tree, int, bool *);
+static tree handle_target_version_attribute (tree *, tree, tree, int, bool *);
 static tree handle_target_clones_attribute (tree *, tree, tree, int, bool *);
 static tree handle_optimize_attribute (tree *, tree, tree, int, bool *);
 static tree ignore_attribute (tree *, tree, tree, int, bool *);
@@ -233,14 +234,23 @@  static const struct attribute_spec::exclusions attr_noinline_exclusions[] =
 
 static const struct attribute_spec::exclusions attr_target_exclusions[] =
 {
-  ATTR_EXCL ("target_clones", true, true, true),
+  ATTR_EXCL ("target_clones", TARGET_HAS_FMV_TARGET_ATTRIBUTE,
+	     TARGET_HAS_FMV_TARGET_ATTRIBUTE, TARGET_HAS_FMV_TARGET_ATTRIBUTE),
   ATTR_EXCL (NULL, false, false, false),
 };
 
 static const struct attribute_spec::exclusions attr_target_clones_exclusions[] =
 {
   ATTR_EXCL ("always_inline", true, true, true),
-  ATTR_EXCL ("target", true, true, true),
+  ATTR_EXCL ("target", TARGET_HAS_FMV_TARGET_ATTRIBUTE,
+	     TARGET_HAS_FMV_TARGET_ATTRIBUTE, TARGET_HAS_FMV_TARGET_ATTRIBUTE),
+  ATTR_EXCL ("target_version", true, true, true),
+  ATTR_EXCL (NULL, false, false, false),
+};
+
+static const struct attribute_spec::exclusions attr_target_version_exclusions[] =
+{
+  ATTR_EXCL ("target_clones", true, true, true),
   ATTR_EXCL (NULL, false, false, false),
 };
 
@@ -512,6 +522,9 @@  const struct attribute_spec c_common_gnu_attributes[] =
   { "target",                 1, -1, true, false, false, false,
 			      handle_target_attribute,
 			      attr_target_exclusions },
+  { "target_version",         1, 1, true, false, false, false,
+			      handle_target_version_attribute,
+			      attr_target_version_exclusions },
   { "target_clones",          1, -1, true, false, false, false,
 			      handle_target_clones_attribute,
 			      attr_target_clones_exclusions },
@@ -5830,7 +5843,7 @@  static tree
 handle_target_attribute (tree *node, tree name, tree args, int flags,
 			 bool *no_add_attrs)
 {
-  /* Ensure we have a function type.  */
+  /* Ensure we have a function declaration.  */
   if (TREE_CODE (*node) != FUNCTION_DECL)
     {
       warning (OPT_Wattributes, "%qE attribute ignored", name);
@@ -5856,13 +5869,32 @@  handle_target_attribute (tree *node, tree name, tree args, int flags,
   return NULL_TREE;
 }
 
+/* Handle a "target_version" attribute.  */
+
+static tree
+handle_target_version_attribute (tree *node, tree name, tree args, int flags,
+				  bool *no_add_attrs)
+{
+  /* Ensure we have a function declaration.  */
+  if (TREE_CODE (*node) != FUNCTION_DECL)
+    {
+      warning (OPT_Wattributes, "%qE attribute ignored", name);
+      *no_add_attrs = true;
+    }
+  else if (!targetm.target_option.valid_version_attribute_p (*node, name, args,
+							     flags))
+    *no_add_attrs = true;
+
+  return NULL_TREE;
+}
+
 /* Handle a "target_clones" attribute.  */
 
 static tree
 handle_target_clones_attribute (tree *node, tree name, tree ARG_UNUSED (args),
 			  int ARG_UNUSED (flags), bool *no_add_attrs)
 {
-  /* Ensure we have a function type.  */
+  /* Ensure we have a function declaration.  */
   if (TREE_CODE (*node) == FUNCTION_DECL)
     {
       for (tree t = args; t != NULL_TREE; t = TREE_CHAIN (t))
diff --git a/gcc/cgraphclones.cc b/gcc/cgraphclones.cc
index 29d28ef895a73a223695cbb86aafbc845bbe7688..3ba5a4a2dd9fd4b7206d95f01c990818d0440334 100644
--- a/gcc/cgraphclones.cc
+++ b/gcc/cgraphclones.cc
@@ -78,6 +78,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "tree-eh.h"
 #include "tree-cfg.h"
 #include "tree-inline.h"
+#include "attribs.h"
 #include "dumpfile.h"
 #include "gimple-pretty-print.h"
 #include "alloc-pool.h"
@@ -1048,7 +1049,17 @@  cgraph_node::create_version_clone_with_body
       location_t saved_loc = input_location;
       tree v = TREE_VALUE (target_attributes);
       input_location = DECL_SOURCE_LOCATION (new_decl);
-      bool r = targetm.target_option.valid_attribute_p (new_decl, NULL, v, 1);
+      bool r;
+      tree name_id = get_attribute_name (target_attributes);
+      const char *name_str = IDENTIFIER_POINTER (name_id);
+      if (strcmp (name_str, "target") == 0)
+	r = targetm.target_option.valid_attribute_p (new_decl, name_id, v, 1);
+      else if (strcmp (name_str, "target_version") == 0)
+	r = targetm.target_option.valid_version_attribute_p (new_decl, name_id,
+							     v, 1);
+      else
+	gcc_unreachable();
+
       input_location = saved_loc;
       if (!r)
 	return NULL;
diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
index bee84879023f30145f4bbb5e0ace8fce871cd919..0850d3f5bce5d0db3f60f375c3da93d71b4587e3 100644
--- a/gcc/cp/decl2.cc
+++ b/gcc/cp/decl2.cc
@@ -834,8 +834,8 @@  check_classfn (tree ctype, tree function, tree template_parms)
       tree c2 = get_constraints (fndecl);
 
       /* While finding a match, same types and params are not enough
-	 if the function is versioned.  Also check version ("target")
-	 attributes.  */
+	 if the function is versioned.  Also check for different target
+	 specific attributes.  */
       if (same_type_p (TREE_TYPE (TREE_TYPE (function)),
 		       TREE_TYPE (TREE_TYPE (fndecl)))
 	  && compparms (p1, p2)
diff --git a/gcc/d/d-attribs.cc b/gcc/d/d-attribs.cc
index 3b69c530c1d29f880b7dcdd5b6e9188f07e4c2e8..52f3b27baa91dbf68d94dca5c3adaaaa32117a67 100644
--- a/gcc/d/d-attribs.cc
+++ b/gcc/d/d-attribs.cc
@@ -128,14 +128,16 @@  static const struct attribute_spec::exclusions attr_noinline_exclusions[] =
 
 static const struct attribute_spec::exclusions attr_target_exclusions[] =
 {
-  ATTR_EXCL ("target_clones", true, true, true),
+  ATTR_EXCL ("target_clones", TARGET_HAS_FMV_TARGET_ATTRIBUTE,
+	     TARGET_HAS_FMV_TARGET_ATTRIBUTE, TARGET_HAS_FMV_TARGET_ATTRIBUTE),
   ATTR_EXCL (NULL, false, false, false),
 };
 
 static const struct attribute_spec::exclusions attr_target_clones_exclusions[] =
 {
   ATTR_EXCL ("always_inline", true, true, true),
-  ATTR_EXCL ("target", true, true, true),
+  ATTR_EXCL ("target", TARGET_HAS_FMV_TARGET_ATTRIBUTE,
+	     TARGET_HAS_FMV_TARGET_ATTRIBUTE, TARGET_HAS_FMV_TARGET_ATTRIBUTE),
   ATTR_EXCL (NULL, false, false, false),
 };
 
diff --git a/gcc/defaults.h b/gcc/defaults.h
index dc6f09cacae8a85873c6d6685eea91fb749cc367..6f0959694102ca28fd993aaff7ec3f5d09334c39 100644
--- a/gcc/defaults.h
+++ b/gcc/defaults.h
@@ -877,6 +877,16 @@  see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 #endif
 #endif
 
+/* Indicate whether the target uses "target" attributes for function
+   multiversioning.  This is used to choose between the "target" and
+   "target_version" attributes when expanding a "target_clones" attribute, and
+   determine whether the "target" and "target_clones" attributes are mutually
+   exclusive.  */
+#ifndef TARGET_HAS_FMV_TARGET_ATTRIBUTE
+#define TARGET_HAS_FMV_TARGET_ATTRIBUTE 1
+#endif
+
+
 /* Select a format to encode pointers in exception handling data.  We
    prefer those that result in fewer dynamic relocations.  Assume no
    special support here and encode direct references.  */
diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index 0bb6da080f8492629537b6c0160674edb425de9c..22a1bebc0f432497d59318ca9ca6a7d9da528d2e 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -10741,6 +10741,18 @@  the function declaration to hold a pointer to a target-specific
 @code{struct cl_target_option} structure.
 @end deftypefn
 
+@deftypefn {Target Hook} bool TARGET_OPTION_VALID_VERSION_ATTRIBUTE_P (tree @var{fndecl}, tree @var{name}, tree @var{args}, int @var{flags})
+This hook is called to parse @code{attribute(target_version("..."))},
+which allows setting target-specific options on individual function versions.
+These function-specific options may differ
+from the options specified on the command line.  The hook should return
+@code{true} if the options are valid.
+
+The hook should set the @code{DECL_FUNCTION_SPECIFIC_TARGET} field in
+the function declaration to hold a pointer to a target-specific
+@code{struct cl_target_option} structure.
+@end deftypefn
+
 @deftypefn {Target Hook} void TARGET_OPTION_SAVE (struct cl_target_option *@var{ptr}, struct gcc_options *@var{opts}, struct gcc_options *@var{opts_set})
 This hook is called to save any additional target-specific information
 in the @code{struct cl_target_option} structure for function-specific
diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
index 4fe0805394ea4d45dfa6ffb41eb052fd32649a0d..f92cb7678784613e1d192c0eb12e1cf67084f16e 100644
--- a/gcc/doc/tm.texi.in
+++ b/gcc/doc/tm.texi.in
@@ -7066,6 +7066,8 @@  on this implementation detail.
 
 @hook TARGET_OPTION_VALID_ATTRIBUTE_P
 
+@hook TARGET_OPTION_VALID_VERSION_ATTRIBUTE_P
+
 @hook TARGET_OPTION_SAVE
 
 @hook TARGET_OPTION_RESTORE
diff --git a/gcc/multiple_target.cc b/gcc/multiple_target.cc
index a2ed048d7dd28ec470953fcd8a0dc86817e4b7dc..a832d1e8ff83d99acd7579c276b083141102f3e1 100644
--- a/gcc/multiple_target.cc
+++ b/gcc/multiple_target.cc
@@ -66,10 +66,6 @@  create_dispatcher_calls (struct cgraph_node *node)
 {
   ipa_ref *ref;
 
-  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),
@@ -377,6 +373,8 @@  expand_target_clones (struct cgraph_node *node, bool definition)
       return false;
     }
 
+  const char *new_attr_name = (TARGET_HAS_FMV_TARGET_ATTRIBUTE
+			       ? "target" : "target_version");
   cgraph_function_version_info *decl1_v = NULL;
   cgraph_function_version_info *decl2_v = NULL;
   cgraph_function_version_info *before = NULL;
@@ -392,7 +390,7 @@  expand_target_clones (struct cgraph_node *node, bool definition)
       char *attr = attrs[i];
 
       /* Create new target clone.  */
-      tree attributes = make_attribute ("target", attr,
+      tree attributes = make_attribute (new_attr_name, attr,
 					DECL_ATTRIBUTES (node->decl));
 
       char *suffix = XNEWVEC (char, strlen (attr) + 1);
@@ -430,7 +428,7 @@  expand_target_clones (struct cgraph_node *node, bool definition)
   XDELETEVEC (attr_str);
 
   /* Setting new attribute to initial function.  */
-  tree attributes = make_attribute ("target", "default",
+  tree attributes = make_attribute (new_attr_name, "default",
 				    DECL_ATTRIBUTES (node->decl));
   DECL_ATTRIBUTES (node->decl) = attributes;
   node->local = false;
diff --git a/gcc/target.def b/gcc/target.def
index ca030abab6c6694ded178a62dbc4e1dbd20540ca..0509e07d6b80bc2fddcb12ccfd3d5bcb7eb16d57 100644
--- a/gcc/target.def
+++ b/gcc/target.def
@@ -6635,6 +6635,23 @@  the function declaration to hold a pointer to a target-specific\n\
  bool, (tree fndecl, tree name, tree args, int flags),
  default_target_option_valid_attribute_p)
 
+/* Function to validate the attribute((target_version(...))) strings.  If
+   the option is validated, the hook should also fill in
+   DECL_FUNCTION_SPECIFIC_TARGET in the function decl node.  */
+DEFHOOK
+(valid_version_attribute_p,
+ "This hook is called to parse @code{attribute(target_version(\"...\"))},\n\
+which allows setting target-specific options on individual function versions.\n\
+These function-specific options may differ\n\
+from the options specified on the command line.  The hook should return\n\
+@code{true} if the options are valid.\n\
+\n\
+The hook should set the @code{DECL_FUNCTION_SPECIFIC_TARGET} field in\n\
+the function declaration to hold a pointer to a target-specific\n\
+@code{struct cl_target_option} structure.",
+ bool, (tree fndecl, tree name, tree args, int flags),
+ default_target_option_valid_version_attribute_p)
+
 /* Function to save any extra target state in the target options structure.  */
 DEFHOOK
 (save,
diff --git a/gcc/targhooks.h b/gcc/targhooks.h
index 5a39e8e6d540714678f912340fb35b65e88757a2..7996cb4433662d5fd76c0eeef2e146802ca9d055 100644
--- a/gcc/targhooks.h
+++ b/gcc/targhooks.h
@@ -193,6 +193,7 @@  extern bool default_hard_regno_scratch_ok (unsigned int);
 extern bool default_mode_dependent_address_p (const_rtx, addr_space_t);
 extern bool default_new_address_profitable_p (rtx, rtx_insn *, rtx);
 extern bool default_target_option_valid_attribute_p (tree, tree, tree, int);
+extern bool default_target_option_valid_version_attribute_p (tree, tree, tree, int);
 extern bool default_target_option_pragma_parse (tree, tree);
 extern bool default_target_can_inline_p (tree, tree);
 extern bool default_update_ipa_fn_target_info (unsigned int &, const gimple *);
diff --git a/gcc/targhooks.cc b/gcc/targhooks.cc
index afe91fe1d17a2dd263a6864bd15bc61c08954b36..1cc0895ceab4b431aebcec9bbebc79cd68af2696 100644
--- a/gcc/targhooks.cc
+++ b/gcc/targhooks.cc
@@ -1799,7 +1799,19 @@  default_target_option_valid_attribute_p (tree ARG_UNUSED (fndecl),
 					 int ARG_UNUSED (flags))
 {
   warning (OPT_Wattributes,
-	   "target attribute is not supported on this machine");
+	   "%<target%> attribute is not supported on this machine");
+
+  return false;
+}
+
+bool
+default_target_option_valid_version_attribute_p (tree ARG_UNUSED (fndecl),
+						 tree ARG_UNUSED (name),
+						 tree ARG_UNUSED (args),
+						 int ARG_UNUSED (flags))
+{
+  warning (OPT_Wattributes,
+	   "%<target_version%> attribute is not supported on this machine");
 
   return false;
 }
diff --git a/gcc/tree.h b/gcc/tree.h
index cec9f6c54a7a53148e5114140356a6a94b6e9746..4829f84c975f7f4e75fb7937a7338e3d9323a50e 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -3510,8 +3510,8 @@  extern vec<tree, va_gc> **decl_debug_args_insert (tree);
    (FUNCTION_DECL_CHECK (NODE)->function_decl.function_specific_optimization)
 
 /* In FUNCTION_DECL, this is set if this function has other versions generated
-   using "target" attributes.  The default version is the one which does not
-   have any "target" attribute set. */
+   to support different architecture feature sets, e.g. using "target" or
+   "target_version" attributes.  */
 #define DECL_FUNCTION_VERSIONED(NODE)\
    (FUNCTION_DECL_CHECK (NODE)->function_decl.versioned_function)