@@ -43,6 +43,7 @@ along with GCC; see the file COPYING3. If not see
#include "context.h"
#include "tree-pass.h"
#include "internal-fn.h"
+#include "calls.h"
/* The gimplification pass converts the language-dependent trees
(ld-trees) emitted by the parser into language-independent trees
@@ -904,6 +905,16 @@ c_gimplify_expr (tree *expr_p, gimple_seq *pre_p ATTRIBUTE_UNUSED,
case CALL_EXPR:
{
tree fndecl = get_callee_fndecl (*expr_p);
+
+ /* Change any calls to a multiversioned function to instead
+ * be a call to the dispatched symbol. */
+ if (fndecl && DECL_FUNCTION_VERSIONED (fndecl))
+ if (tree dis = get_c_function_version_dispatcher (fndecl))
+ {
+ dis = build_fold_addr_expr_loc (EXPR_LOCATION (dis), dis);
+ CALL_EXPR_FN (*expr_p) = dis;
+ }
+
if (fndecl
&& fndecl_built_in_p (fndecl, BUILT_IN_CLZG, BUILT_IN_CTZG)
&& call_expr_nargs (*expr_p) == 2
@@ -62,6 +62,7 @@ along with GCC; see the file COPYING3. If not see
#include "omp-general.h"
#include "omp-offload.h" /* For offload_vars. */
#include "c-parser.h"
+#include "tree.h"
#include "tree-pretty-print.h"
@@ -2098,6 +2099,22 @@ previous_tag (tree type)
return NULL_TREE;
}
+/* Subroutine to mark functions as versioned when using the attribute
+ 'target_version'. */
+
+static void
+maybe_mark_function_versioned (tree decl)
+{
+ if (!DECL_FUNCTION_VERSIONED (decl))
+ {
+ DECL_FUNCTION_VERSIONED (decl) = true;
+
+ tree mangled_name
+ = targetm.mangle_decl_assembler_name (decl, DECL_NAME (decl));
+ SET_DECL_ASSEMBLER_NAME (decl, mangled_name);
+ }
+}
+
/* Subroutine of duplicate_decls. Compare NEWDECL to OLDDECL.
Returns true if the caller should proceed to merge the two, false
if OLDDECL should simply be discarded. As a side effect, issues
@@ -2418,7 +2435,39 @@ diagnose_mismatched_decls (tree newdecl, tree olddecl,
}
}
- if (DECL_INITIAL (newdecl))
+ if ((DECL_INITIAL (newdecl) || DECL_EXTERNAL (newdecl))
+ && (DECL_INITIAL (olddecl) || DECL_EXTERNAL (olddecl))
+ && targetm.target_option.function_versions (newdecl, olddecl))
+ {
+ /* Check if we are dealing with Function Multi Versioning using
+ the 'target_version' attribute. */
+ tree olddecl_args = TYPE_ARG_TYPES (TREE_TYPE (olddecl));
+ tree newdecl_args = TYPE_ARG_TYPES (TREE_TYPE (newdecl));
+ /* Function Multi Versioned functions need to have the same
+ prototype and the target must support these too, otherwise
+ error.
+ TODO: should we be checking any other properties? */
+ if (!comptypes (TREE_TYPE (olddecl), TREE_TYPE (newdecl))
+ || !comptypes (olddecl_args, newdecl_args))
+ {
+ auto_diagnostic_group d;
+ error ("redefinition of %q+D", newdecl);
+ locate_old_decl (olddecl);
+ return false;
+ }
+ /* Only record if we are seeing either of these for the first
+ time. */
+ bool record = (!DECL_FUNCTION_VERSIONED (newdecl)
+ || !DECL_FUNCTION_VERSIONED (olddecl));
+
+ maybe_mark_function_versioned (newdecl);
+ maybe_mark_function_versioned (olddecl);
+
+ if (record)
+ cgraph_node::record_function_versions (olddecl, newdecl);
+ }
+
+ else if (DECL_INITIAL (newdecl))
{
if (DECL_INITIAL (olddecl))
{
@@ -3191,6 +3240,14 @@ duplicate_decls (tree newdecl, tree olddecl)
return false;
}
+ /* If both new and old are Function Multi Versioned functions then they are
+ not duplicates. */
+ if (TREE_CODE (newdecl) == FUNCTION_DECL
+ && TREE_CODE (olddecl) == FUNCTION_DECL
+ && DECL_FUNCTION_VERSIONED (newdecl)
+ && DECL_FUNCTION_VERSIONED (olddecl))
+ return false;
+
merge_decls (newdecl, olddecl, newtype, oldtype);
/* The NEWDECL will no longer be needed.
@@ -13686,6 +13743,10 @@ c_parse_final_cleanups (void)
c_write_global_declarations_1 (BLOCK_VARS (DECL_INITIAL (t)));
c_write_global_declarations_1 (BLOCK_VARS (ext_block));
+ /* Call this to set cpp_implicit_aliases_done on all nodes. This is
+ important for function multiversioning aliases to get resolved. */
+ symtab->process_same_body_aliases ();
+
if (!in_lto_p)
free_attr_access_data ();
@@ -5386,3 +5386,26 @@ cxx17_empty_base_field_p (const_tree field)
&& RECORD_OR_UNION_TYPE_P (TREE_TYPE (field))
&& !lookup_attribute ("no_unique_address", DECL_ATTRIBUTES (field)));
}
+
+/* Returns the decl of the dispatcher function if FN is a function version. */
+
+tree
+get_c_function_version_dispatcher (tree fn)
+{
+ tree dispatcher_decl = NULL;
+
+ gcc_assert (TREE_CODE (fn) == FUNCTION_DECL
+ && DECL_FUNCTION_VERSIONED (fn));
+
+ gcc_assert (targetm.get_function_versions_dispatcher);
+ dispatcher_decl = targetm.get_function_versions_dispatcher (fn);
+
+ if (dispatcher_decl == NULL)
+ {
+ error_at (input_location, "use of multiversioned function "
+ "without a default");
+ return NULL;
+ }
+
+ return dispatcher_decl;
+}
@@ -134,5 +134,6 @@ extern void maybe_complain_about_tail_call (tree, const char *);
extern rtx rtx_for_static_chain (const_tree, bool);
extern bool cxx17_empty_base_field_p (const_tree);
+extern tree get_c_function_version_dispatcher (tree fn);
#endif // GCC_CALLS_H
new file mode 100644
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__((target_version("default")))
+int foo ()
+{
+ return 1;
+}
+
+/* It is not overly clear what the correct behaviour is in this case.
+ This test serves more as a test of consistency for this case rather
+ than a test of correctness. */
+
+/* { dg-final { scan-assembler-times "\n_Z3foov:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.default:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n_Z3foov\.resolver:\n" 0 } } */
new file mode 100644
@@ -0,0 +1,39 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__((target_version("default")))
+int foo ()
+{
+ return 1;
+}
+
+__attribute__((target_version("rng")))
+int foo ()
+{
+ return 1;
+}
+
+__attribute__((target_version("flagm")))
+int foo ()
+{
+ return 1;
+}
+
+__attribute__((target_version("rng+flagm")))
+int foo ()
+{
+ return 1;
+}
+
+int bar()
+{
+ return foo ();
+}
+
+/* Check usage of the first two FMV features, in case of off-by-one errors. */
+/* { dg-final { scan-assembler-times "\nfoo\.default:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._Mrng:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._MrngMflagm:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._Mflagm:\n" 1 } } */
+
+/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 1 } } */
new file mode 100644
@@ -0,0 +1,37 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+// Basic case of fmv correctness with all functions and use in one TU.
+
+__attribute__((target_version("default")))
+int foo ()
+{
+ return 1;
+}
+
+__attribute__((target_version("dotprod")))
+int foo ()
+{
+ return 3;
+}
+__attribute__((target_version("sve+sve2")))
+int foo ()
+{
+ return 5;
+}
+
+int bar()
+{
+ return foo ();
+}
+
+/* When updating any of the symbol names in these tests, make sure to also
+ update any tests for their absence in mv-symbolsN.C */
+
+/* { dg-final { scan-assembler-times "\nfoo\.default:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._Mdotprod:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._MsveMsve2:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tbl\tfoo\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\tfoo, %gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\tfoo,foo\.resolver\n" 1 } } */
new file mode 100644
@@ -0,0 +1,28 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+// FMV correctness with definitions but no call
+
+__attribute__((target_version("default")))
+int foo ()
+{
+ return 1;
+}
+
+__attribute__((target_version("dotprod")))
+int foo ()
+{
+ return 3;
+}
+__attribute__((target_version("sve+sve2")))
+int foo ()
+{
+ return 5;
+}
+
+/* { dg-final { scan-assembler-times "\nfoo\.default:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._Mdotprod:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._MsveMsve2:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\tfoo, %gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\tfoo,foo\.resolver\n" 0 } } */
new file mode 100644
@@ -0,0 +1,26 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+// FMV correctness with declarations but no implementation
+
+__attribute__((target_version("default")))
+int foo ();
+
+__attribute__((target_version("dotprod")))
+int foo ();
+
+__attribute__((target_version("sve+sve2")))
+int foo ();
+
+int bar()
+{
+ return foo ();
+}
+
+/* { dg-final { scan-assembler-times "\nfoo\.default:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._Mdotprod:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._MsveMsve2:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tbl\tfoo\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\tfoo, %gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\tfoo,foo\.resolver\n" 1 } } */
new file mode 100644
@@ -0,0 +1,29 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+// FMV correctness with a default implementation and declarations of other versions
+
+__attribute__((target_version("default")))
+int foo ()
+{
+ return 1;
+}
+
+__attribute__((target_version("dotprod")))
+int foo ();
+
+__attribute__((target_version("sve+sve2")))
+int foo ();
+
+int bar()
+{
+ return foo ();
+}
+
+/* { dg-final { scan-assembler-times "\nfoo\.default:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._Mdotprod:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._MsveMsve2:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tbl\tfoo\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\tfoo, %gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\tfoo,foo\.resolver\n" 1 } } */
new file mode 100644
@@ -0,0 +1,35 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+// FMV correctness with default declaration, and implementations of other
+// versions.
+
+__attribute__((target_version("default")))
+int foo ();
+
+__attribute__((target_version("dotprod")))
+int foo ()
+{
+ return 3;
+}
+__attribute__((target_version("sve+sve2")))
+int foo ()
+{
+ return 5;
+}
+
+int bar()
+{
+ return foo ();
+}
+
+/* When updating any of the symbol names in these tests, make sure to also
+ update any tests for their absence in mvc-symbolsN.C */
+
+/* { dg-final { scan-assembler-times "\nfoo\.default:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._Mdotprod:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._MsveMsve2:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tbl\tfoo\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\tfoo, %gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\tfoo,foo\.resolver\n" 1 } } */
new file mode 100644
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__((target_version("default")))
+int foo ()
+{
+ return 1;
+}
+
+
+/* It is not overly clear what the correct behaviour is in this case.
+ This test serves more as a test of consistency for this case rather
+ than a test of correctness. */
+
+/* { dg-final { scan-assembler-times "\nfoo:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\.default:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 0 } } */
new file mode 100644
@@ -0,0 +1,25 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__((target_clones("default", "dotprod", "sve+sve2")))
+int foo ()
+{
+ return 1;
+}
+
+int bar()
+{
+ return foo ();
+}
+
+
+/* When updating any of the symbol names in these tests, make sure to also
+ update any tests for their absence in mvc-symbolsN.C */
+
+/* { dg-final { scan-assembler-times "\nfoo\.default:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._Mdotprod:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._MsveMsve2:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tbl\tfoo\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\tfoo, %gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\tfoo,foo\.resolver\n" 1 } } */
new file mode 100644
@@ -0,0 +1,16 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__((target_clones("default", "dotprod", "sve+sve2")))
+int foo ()
+{
+ return 1;
+}
+
+
+/* { dg-final { scan-assembler-times "\nfoo\.default:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._Mdotprod:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._MsveMsve2:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\tfoo, %gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\tfoo,foo\.resolver\n" 1 } } */
new file mode 100644
@@ -0,0 +1,19 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__((target_clones("default", "dotprod", "sve+sve2")))
+int foo ();
+
+int bar()
+{
+ return foo ();
+}
+
+/* { dg-final { scan-assembler-times "\nfoo\.default:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._Mdotprod:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._MsveMsve2:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tbl\tfoo\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\tfoo, %gnu_indirect_function\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\tfoo,foo\.resolver\n" 1 } } */
+
new file mode 100644
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__((target_clones("default", "dotprod", "sve+sve2")))
+int foo ();
+
+/* { dg-final { scan-assembler-times "\nfoo\.default:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._Mdotprod:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\nfoo\._MsveMsve2:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\nfoo\.resolver:\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.type\tfoo, %gnu_indirect_function\n" 0 } } */
+/* { dg-final { scan-assembler-times "\n\t\.set\tfoo,foo\.resolver\n" 0 } } */