@@ -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
@@ -3365,11 +3382,53 @@ pushdecl (tree x)
b->inner_comp = false;
b_use = b;
b_ext = b;
+
+ /* Check if the function is part of a group of multiversioned functions.
+ If so, we should check for conflicts or duplicates between any with
+ the same version. If there are no conflicts, should just add the new
+ binding. */
+ if (b && TREE_CODE (x) == FUNCTION_DECL
+ && TREE_CODE (b->decl) == FUNCTION_DECL
+ && (DECL_FUNCTION_VERSIONED (b->decl)
+ || targetm.target_option.function_versions (x, b->decl)))
+ {
+ maybe_mark_function_versioned (x);
+ for (; b_use; b_use = b_use->shadowed)
+ {
+ if (!comptypes (type, TREE_TYPE (b_use->decl))
+ || TREE_CODE (b_use->decl) != FUNCTION_DECL)
+ {
+ /* Found a conflicting declaration.
+ duplicate_decls will create teh diagnostics forthis. */
+ b = b_use;
+ break;
+ }
+ maybe_mark_function_versioned (b_use->decl);
+ cgraph_node::record_function_versions (b_use->decl, x);
+
+ if (!targetm.target_option.function_versions (x, b_use->decl))
+ {
+ /* Found a duplicate binding for the same version. */
+ b = b_use;
+ break;
+ }
+ }
+ /* If no conflicting decl is found, then skip duplicate declaration
+ logic and record the new binding. */
+ if (!b_use)
+ {
+ b_use = b;
+ goto skip_external_and_shadow_checks;
+ }
+ b_use = b;
+ b_ext = b;
+ }
+
/* If this is an external linkage declaration, we should check
for compatibility with the type in the external scope before
setting the type at this scope based on the visible
information only. */
- if (TREE_PUBLIC (x) && TREE_PUBLIC (visdecl))
+ else if (TREE_PUBLIC (x) && TREE_PUBLIC (visdecl))
{
while (b_ext && !B_IN_EXTERNAL_SCOPE (b_ext))
b_ext = b_ext->shadowed;
@@ -13686,6 +13745,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,25 @@ 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,7 @@ 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,40 @@
+/* { 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,11 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__ ((target_version ("default"))) int
+foo ();
+
+__attribute__ ((target_version ("default"))) int
+foo () { return 1; } /* { dg-message "note: previous definition of 'foo'" } */
+
+__attribute__ ((target_version ("dotprod"))) float
+foo () { return 3; } /* { dg-error "conflicting types for 'foo'" } */
new file mode 100644
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__ ((target_version ("dotprod"))) float
+foo () { return 3; } /* { dg-message "note: previous definition of 'foo'" } */
+
+__attribute__ ((target_version ("dotprod"))) float
+foo () { return 3; } /* { dg-error "redefinition of 'foo'" } */
new file mode 100644
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__ ((target_version ("default"))) int
+foo (int a, int b) { return 1; } /* { dg-message "note: previous definition of 'foo'" } */
+
+__attribute__ ((target_version ("dotprod"))) int
+foo (int a, float c) { return 3; } /* { dg-error "conflicting types for 'foo'" } */
new file mode 100644
@@ -0,0 +1,8 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__ ((target_version ("dotprod"))) int
+foo (int a, int b); /* { dg-message "note: previous declaration of 'foo'" } */
+
+__attribute__ ((target_version ("dotprod"))) int
+foo (int a, float c) { return 3; } /* { dg-error "conflicting types for 'foo'" } */
new file mode 100644
@@ -0,0 +1,11 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__ ((target_version ("default"))) int
+foo () { return 1; } /* { dg-message "note: previous definition of 'foo'" } */
+
+__attribute__ ((target_version ("dotprod"))) float
+foo (); /* { dg-error "conflicting types for 'foo'" } */
+
+__attribute__ ((target_version ("dotprod"))) float
+foo () { return 3.0; }
new file mode 100644
@@ -0,0 +1,38 @@
+/* { 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,42 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+int
+foo ();
+
+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 ();
+}
+
+/* { 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\tadrp\tx., foo\._MsveMsve2\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx., foo\._Mdotprod\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx., foo\.default\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,27 @@
+/* { 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,31 @@
+/* { 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,36 @@
+/* { 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,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 "\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,47 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__ ((target_version ("dotprod"))) int
+foo ();
+
+__attribute__ ((target_version ("sve+sve2"))) int
+foo ();
+
+__attribute__ ((target_version ("default"))) int
+foo ();
+
+__attribute__ ((target_version ("sve+sve2"))) int
+foo ()
+{
+ return 5;
+}
+__attribute__ ((target_version ("dotprod"))) int
+foo ()
+{
+ return 3;
+}
+__attribute__ ((target_version ("default"))) int
+foo ()
+{
+ return 1;
+}
+
+int
+bar ()
+{
+ return foo ();
+}
+
+/* { 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\tadrp\tx., foo\._MsveMsve2\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx., foo\._Mdotprod\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx., foo\.default\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,44 @@
+/* { dg-do compile } */
+/* { dg-options "-O0" } */
+
+__attribute__ ((target_version ("dotprod"))) int
+foo ();
+__attribute__ ((target_version ("sve+sve2"))) int
+foo ();
+
+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 ();
+}
+
+/* { 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\tadrp\tx., foo\._MsveMsve2\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx., foo\._Mdotprod\n" 1 } } */
+/* { dg-final { scan-assembler-times "\n\tadrp\tx., foo\.default\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,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,15 @@
+/* { 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 } } */