get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/2226680/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 2226680,
    "url": "http://patchwork.ozlabs.org/api/patches/2226680/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/gcc/patch/bmm.hhuodmon6a.gcc.gcc-TEST.alfie.richards.49.1.13@forge-stage.sourceware.org/",
    "project": {
        "id": 17,
        "url": "http://patchwork.ozlabs.org/api/projects/17/?format=api",
        "name": "GNU Compiler Collection",
        "link_name": "gcc",
        "list_id": "gcc-patches.gcc.gnu.org",
        "list_email": "gcc-patches@gcc.gnu.org",
        "web_url": null,
        "scm_url": null,
        "webscm_url": null,
        "list_archive_url": "",
        "list_archive_url_format": "",
        "commit_url_format": ""
    },
    "msgid": "<bmm.hhuodmon6a.gcc.gcc-TEST.alfie.richards.49.1.13@forge-stage.sourceware.org>",
    "list_archive_url": null,
    "date": "2026-04-22T18:22:01",
    "name": "[v1,13/13] FMV: Redirect to specific target",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "35cd4b5e311021752b2a4d5ac88a41ee010455dd",
    "submitter": {
        "id": 93228,
        "url": "http://patchwork.ozlabs.org/api/people/93228/?format=api",
        "name": "\\\"alfie.richards via Sourceware Forge\\\"",
        "email": "forge-bot+alfie.richards@forge-stage.sourceware.org"
    },
    "delegate": null,
    "mbox": "http://patchwork.ozlabs.org/project/gcc/patch/bmm.hhuodmon6a.gcc.gcc-TEST.alfie.richards.49.1.13@forge-stage.sourceware.org/mbox/",
    "series": [
        {
            "id": 501072,
            "url": "http://patchwork.ozlabs.org/api/series/501072/?format=api",
            "web_url": "http://patchwork.ozlabs.org/project/gcc/list/?series=501072",
            "date": "2026-04-22T18:21:54",
            "name": "FMV refactor and ACLE compliance for C++",
            "version": 1,
            "mbox": "http://patchwork.ozlabs.org/series/501072/mbox/"
        }
    ],
    "comments": "http://patchwork.ozlabs.org/api/patches/2226680/comments/",
    "check": "pending",
    "checks": "http://patchwork.ozlabs.org/api/patches/2226680/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "<gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org>",
        "X-Original-To": [
            "incoming@patchwork.ozlabs.org",
            "gcc-patches@gcc.gnu.org"
        ],
        "Delivered-To": [
            "patchwork-incoming@legolas.ozlabs.org",
            "gcc-patches@gcc.gnu.org"
        ],
        "Authentication-Results": [
            "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org\n (client-ip=2620:52:6:3111::32; helo=vm01.sourceware.org;\n envelope-from=gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org;\n receiver=patchwork.ozlabs.org)",
            "sourceware.org; dmarc=none (p=none dis=none)\n header.from=forge-stage.sourceware.org",
            "sourceware.org;\n spf=pass smtp.mailfrom=forge-stage.sourceware.org",
            "server2.sourceware.org;\n arc=none smtp.remote-ip=38.145.34.39"
        ],
        "Received": [
            "from vm01.sourceware.org (vm01.sourceware.org\n [IPv6:2620:52:6:3111::32])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g174B46RLz1yGs\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 23 Apr 2026 04:27:22 +1000 (AEST)",
            "from vm01.sourceware.org (localhost [127.0.0.1])\n\tby sourceware.org (Postfix) with ESMTP id B776848FEF55\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 22 Apr 2026 18:27:20 +0000 (GMT)",
            "from forge-stage.sourceware.org (vm08.sourceware.org [38.145.34.39])\n by sourceware.org (Postfix) with ESMTPS id ECD2E4422C35\n for <gcc-patches@gcc.gnu.org>; Wed, 22 Apr 2026 18:23:34 +0000 (GMT)",
            "from forge-stage.sourceware.org (localhost [IPv6:::1])\n (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256)\n (No client certificate requested)\n by forge-stage.sourceware.org (Postfix) with ESMTPS id D6C1C43460\n for <gcc-patches@gcc.gnu.org>; Wed, 22 Apr 2026 18:23:30 +0000 (UTC)"
        ],
        "DKIM-Filter": [
            "OpenDKIM Filter v2.11.0 sourceware.org B776848FEF55",
            "OpenDKIM Filter v2.11.0 sourceware.org ECD2E4422C35"
        ],
        "DMARC-Filter": "OpenDMARC Filter v1.4.2 sourceware.org ECD2E4422C35",
        "ARC-Filter": "OpenARC Filter v1.0.0 sourceware.org ECD2E4422C35",
        "ARC-Seal": "i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1776882215; cv=none;\n b=HwarvfX805C/Ebdw9Yes8eLhgLdtHumtD6dgh1n+HgqaLeK0hooKZszB4EyUaaVZVln8ICal0eupf9GTVC6+A4CRnUrtFbIxwE0/wgN3Pj5Fq4xs+ykVTAHgJllkNJdTlueyDPCMoOZQ8BxCCPvks9n8ZRvu+SGp4Ircv/6Zbfw=",
        "ARC-Message-Signature": "i=1; a=rsa-sha256; d=sourceware.org; s=key;\n t=1776882215; c=relaxed/simple;\n bh=y0T1UxwMSCDdbcnx1KoMsNgTndz4dlRz4D+IbYIWIps=;\n h=From:Date:Subject:To:Message-ID;\n b=NUjUn+UxrKWQgzGpx8MyQGvyrRw8fMwYrK+VplzBf2HBJB5zKlgDRWxpfBCbGnu00M74mG/RUQdY9MJAkhHkHPPXaj+85Vuhog95e6R3SH+V6Awwpus4JaZJkCBmtIvTmGfCw7NA2uSyUr6KEiQcsH8whYWFF19BwG67U/NR+y0=",
        "ARC-Authentication-Results": "i=1; server2.sourceware.org",
        "From": "\"\\\"alfie.richards via Sourceware Forge\\\"\"\n <forge-bot+alfie.richards@forge-stage.sourceware.org>",
        "Date": "Wed, 22 Apr 2026 18:22:01 +0000",
        "Subject": "[PATCH v1 13/13] FMV: Redirect to specific target",
        "To": "gcc-patches mailing list <gcc-patches@gcc.gnu.org>",
        "Message-ID": "\n <bmm.hhuodmon6a.gcc.gcc-TEST.alfie.richards.49.1.13@forge-stage.sourceware.org>",
        "X-Mailer": "batrachomyomachia",
        "X-Requested-Reviewer": [
            "rsandifo",
            "rearnsha"
        ],
        "X-Pull-Request-Organization": "gcc",
        "X-Pull-Request-Repository": "gcc-TEST",
        "X-Pull-Request": "https://forge.sourceware.org/gcc/gcc-TEST/pulls/49",
        "References": "\n <bmm.hhuodmon6a.gcc.gcc-TEST.alfie.richards.49.1.0@forge-stage.sourceware.org>",
        "In-Reply-To": "\n <bmm.hhuodmon6a.gcc.gcc-TEST.alfie.richards.49.1.0@forge-stage.sourceware.org>",
        "X-Patch-URL": "\n https://forge.sourceware.org/alfie.richards/gcc-TEST/commit/d786bf33d2bbdf1562f7913b5e54507ee9ecc6e1",
        "X-BeenThere": "gcc-patches@gcc.gnu.org",
        "X-Mailman-Version": "2.1.30",
        "Precedence": "list",
        "List-Id": "Gcc-patches mailing list <gcc-patches.gcc.gnu.org>",
        "List-Unsubscribe": "<https://gcc.gnu.org/mailman/options/gcc-patches>,\n <mailto:gcc-patches-request@gcc.gnu.org?subject=unsubscribe>",
        "List-Archive": "<https://gcc.gnu.org/pipermail/gcc-patches/>",
        "List-Post": "<mailto:gcc-patches@gcc.gnu.org>",
        "List-Help": "<mailto:gcc-patches-request@gcc.gnu.org?subject=help>",
        "List-Subscribe": "<https://gcc.gnu.org/mailman/listinfo/gcc-patches>,\n <mailto:gcc-patches-request@gcc.gnu.org?subject=subscribe>",
        "Reply-To": "gcc-patches mailing list <gcc-patches@gcc.gnu.org>,\n alfierichards@sourceware.org",
        "Errors-To": "gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org"
    },
    "content": "From: Alfie Richards <alfie.richards@arm.com>\n\nAdds an optimisation in FMV to redirect to a specific target if possible.\n\nA call is redirected to a specific target if both:\n- the caller can always call the callee version\n- and, it is possible to rule out all higher priority versions of the callee\n  fmv set. That is estabilished either by the callee being the highest priority\n  version, or each higher priority version of the callee implying that, were it\n  resolved, a higher priority version of the caller would have been selected.\n\nFor this logic, introduces the new TARGET_OPTION_FUNCTIONS_B_RESOLVABLE_FROM_A\nhook. Adds a full implementation for Aarch64, and a weaker default version\nfor other targets.\n\nThis allows the target to replace the previous optimisation as the new one is\nable to cover the same case where two function sets implement the same versions.\n\ngcc/ChangeLog:\n\n\t* config/aarch64/aarch64.cc (aarch64_functions_b_resolvable_from_a): New\n\tfunction.\n\t(TARGET_OPTION_FUNCTIONS_B_RESOLVABLE_FROM_A): New define.\n\t* doc/tm.texi: Regenerate.\n\t* doc/tm.texi.in: Add documentation for\n\tTARGET_OPTION_FUNCTIONS_B_RESOLVABLE_FROM_A.\n\t* multiple_target.cc (redirect_to_specific_clone): Add new optimisation\n\tlogic.\n\t(ipa_target_clone): Remove check for TARGET_HAS_FMV_TARGET_ATTRIBUTE.\n\t* target.def: Document new hook..\n\t* attribs.cc: (functions_b_resolvable_from_a) New function.\n\t* attribs.h: (functions_b_resolvable_from_a) New function.\n\ngcc/testsuite/ChangeLog:\n\n\t* g++.target/aarch64/fmv-selection1.C: New test.\n\t* g++.target/aarch64/fmv-selection2.C: New test.\n\t* g++.target/aarch64/fmv-selection3.C: New test.\n\t* g++.target/aarch64/fmv-selection4.C: New test.\n\t* g++.target/aarch64/fmv-selection5.C: New test.\n\t* g++.target/aarch64/fmv-selection6.C: New test.\n\t* g++.target/aarch64/fmv-selection7.C: New test.\n---\n gcc/attribs.cc                                |  22 ++++\n gcc/attribs.h                                 |   1 +\n gcc/config/aarch64/aarch64.cc                 |  28 +++++\n gcc/doc/tm.texi                               |  19 +++\n gcc/doc/tm.texi.in                            |   2 +\n gcc/multiple_target.cc                        | 114 ++++++++++++------\n gcc/target.def                                |  22 ++++\n .../g++.target/aarch64/fmv-selection1.C       |  40 ++++++\n .../g++.target/aarch64/fmv-selection2.C       |  40 ++++++\n .../g++.target/aarch64/fmv-selection3.C       |  25 ++++\n .../g++.target/aarch64/fmv-selection4.C       |  30 +++++\n .../g++.target/aarch64/fmv-selection5.C       |  28 +++++\n .../g++.target/aarch64/fmv-selection6.C       |  27 +++++\n .../g++.target/aarch64/fmv-selection7.C       |  65 ++++++++++\n 14 files changed, 425 insertions(+), 38 deletions(-)\n create mode 100644 gcc/testsuite/g++.target/aarch64/fmv-selection1.C\n create mode 100644 gcc/testsuite/g++.target/aarch64/fmv-selection2.C\n create mode 100644 gcc/testsuite/g++.target/aarch64/fmv-selection3.C\n create mode 100644 gcc/testsuite/g++.target/aarch64/fmv-selection4.C\n create mode 100644 gcc/testsuite/g++.target/aarch64/fmv-selection5.C\n create mode 100644 gcc/testsuite/g++.target/aarch64/fmv-selection6.C\n create mode 100644 gcc/testsuite/g++.target/aarch64/fmv-selection7.C",
    "diff": "diff --git a/gcc/attribs.cc b/gcc/attribs.cc\nindex 2ca82674f7c2..7b6ce6e00200 100644\n--- a/gcc/attribs.cc\n+++ b/gcc/attribs.cc\n@@ -1095,6 +1095,28 @@ common_function_versions (string_slice fn1 ATTRIBUTE_UNUSED,\n   gcc_unreachable ();\n }\n \n+/* Default implementation of TARGET_OPTION_FUNCTIONS_B_RESOLVABLE_FROM_A.\n+   Used to check very basically if DECL_B is callable from DECL_A.\n+   For now this checks if the version strings are the same.  */\n+\n+bool\n+functions_b_resolvable_from_a (tree decl_a, tree decl_b,\n+\t\t\t       tree base ATTRIBUTE_UNUSED)\n+{\n+  const char *attr_name = TARGET_HAS_FMV_TARGET_ATTRIBUTE\n+\t\t\t  ? \"target\"\n+\t\t\t  : \"target_version\";\n+\n+  tree attr_a = lookup_attribute (attr_name, DECL_ATTRIBUTES (decl_a));\n+  tree attr_b = lookup_attribute (attr_name, DECL_ATTRIBUTES (decl_b));\n+\n+  gcc_assert (attr_b);\n+  if (!attr_a)\n+    return false;\n+\n+  return attribute_value_equal (attr_a, attr_b);\n+}\n+\n /* Comparator function to be used in qsort routine to sort attribute\n    specification strings to \"target\".  */\n \ndiff --git a/gcc/attribs.h b/gcc/attribs.h\nindex 6e15b48e1ed6..74a793458eb2 100644\n--- a/gcc/attribs.h\n+++ b/gcc/attribs.h\n@@ -57,6 +57,7 @@ extern char *sorted_attr_string (tree);\n extern bool common_function_versions (string_slice, string_slice);\n extern tree make_dispatcher_decl (const tree);\n extern bool is_function_default_version (const tree);\n+extern bool functions_b_resolvable_from_a (tree, tree, tree);\n extern void handle_ignored_attributes_option (vec<char *> *);\n \n /* Return a type like TTYPE except that its TYPE_ATTRIBUTES\ndiff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc\nindex 9dff3ab61e32..30453b46b0dd 100644\n--- a/gcc/config/aarch64/aarch64.cc\n+++ b/gcc/config/aarch64/aarch64.cc\n@@ -20574,6 +20574,30 @@ aarch64_compare_version_priority (tree decl1, tree decl2)\n   return compare_feature_masks (mask1, mask2);\n }\n \n+/* Implement TARGET_OPTION_FUNCTIONS_B_RESOLVABLE_FROM_A.  */\n+\n+bool\n+aarch64_functions_b_resolvable_from_a (tree decl_a, tree decl_b, tree baseline)\n+{\n+  auto baseline_isa = aarch64_get_isa_flags\n+\t\t (TREE_TARGET_OPTION (aarch64_fndecl_options (baseline)));\n+  auto isa_a = baseline_isa;\n+  auto isa_b = baseline_isa;\n+\n+  auto a_version = get_target_version (decl_a);\n+  auto b_version = get_target_version (decl_b);\n+  if (a_version.is_valid ())\n+    aarch64_parse_fmv_features (a_version, &isa_a, NULL, NULL);\n+  if (b_version.is_valid ())\n+    aarch64_parse_fmv_features (b_version, &isa_b, NULL, NULL);\n+\n+  /* Are there any bits of b that arent in a.  */\n+  if (isa_b & (~isa_a))\n+    return false;\n+\n+  return true;\n+}\n+\n /* Build the struct __ifunc_arg_t type:\n \n    struct __ifunc_arg_t\n@@ -32786,6 +32810,10 @@ aarch64_libgcc_floating_mode_supported_p\n #undef TARGET_COMPARE_VERSION_PRIORITY\n #define TARGET_COMPARE_VERSION_PRIORITY aarch64_compare_version_priority\n \n+#undef TARGET_OPTION_FUNCTIONS_B_RESOLVABLE_FROM_A\n+#define TARGET_OPTION_FUNCTIONS_B_RESOLVABLE_FROM_A \\\n+  aarch64_functions_b_resolvable_from_a\n+\n #undef TARGET_GENERATE_VERSION_DISPATCHER_BODY\n #define TARGET_GENERATE_VERSION_DISPATCHER_BODY \\\n   aarch64_generate_version_dispatcher_body\ndiff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi\nindex df89f751d08c..6425e3042e6e 100644\n--- a/gcc/doc/tm.texi\n+++ b/gcc/doc/tm.texi\n@@ -10982,6 +10982,25 @@ This target hook returns @code{true} if the target/target-version strings\n @var{fn1} and @var{fn2} imply the same function version.\n @end deftypefn\n \n+@deftypefn {Target Hook} bool TARGET_OPTION_FUNCTIONS_B_RESOLVABLE_FROM_A (tree @var{decl_a}, tree @var{decl_v}, tree @var{base})\n+@var{decl_b} is a function declaration with a function multi-versioning\n+(FMV) attribute; this attribute is either @code{target} or\n+@code{target_version}, depending on @code{TARGET_HAS_FMV_TARGET_ATTRIBUTE}.\n+@var{decl_a} is a function declaration that may or may not have an FMV\n+attribute.\n+\n+Return true if we have enough information to determine that the\n+requirements of @var{decl_b}'s FMV attribute are met whenever @var{decl_a}\n+is executed, given that the target supports all features required by\n+function declaration @var{base}.\n+\n+The default implementation just checks whether @var{decl_a} has the same\n+FMV attribute as @var{decl_b}.  This is conservatively correct,\n+but ports can do better by taking the relationships between architecture\n+features into account.  For example, on AArch64, @code{sve} is present\n+whenever @code{sve2} is present.\n+@end deftypefn\n+\n @deftypefn {Target Hook} bool TARGET_CAN_INLINE_P (tree @var{caller}, tree @var{callee})\n This target hook returns @code{false} if the @var{caller} function\n cannot inline @var{callee}, based on target specific information.  By\ndiff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in\nindex c2e7921192cc..5d33158bd484 100644\n--- a/gcc/doc/tm.texi.in\n+++ b/gcc/doc/tm.texi.in\n@@ -7142,6 +7142,8 @@ with the target specific attributes.  The default value is @code{','}.\n \n @hook TARGET_OPTION_FUNCTION_VERSIONS\n \n+@hook TARGET_OPTION_FUNCTIONS_B_RESOLVABLE_FROM_A\n+\n @hook TARGET_CAN_INLINE_P\n \n @hook TARGET_UPDATE_IPA_FN_TARGET_INFO\ndiff --git a/gcc/multiple_target.cc b/gcc/multiple_target.cc\nindex e85d7e71442d..7a0d22cfc78a 100644\n--- a/gcc/multiple_target.cc\n+++ b/gcc/multiple_target.cc\n@@ -423,61 +423,100 @@ expand_target_clones (struct cgraph_node *node, bool definition)\n   return true;\n }\n \n-/* When NODE is a target clone, consider all callees and redirect\n-   to a clone with equal target attributes.  That prevents multiple\n-   multi-versioning dispatches and a call-chain can be optimized.\n-\n-   This optimisation might pick the wrong version in some cases, since knowing\n-   that we meet the target requirements for a matching callee version does not\n-   tell us that we won't also meet the target requirements for a higher\n-   priority callee version at runtime.  Since this is longstanding behaviour\n-   for x86 and powerpc, we preserve it for those targets, but skip the optimisation\n-   for targets that use the \"target_version\" attribute for multi-versioning.  */\n+/* When NODE is part of an FMV function set, consider all callees and check if\n+   any can provably always resolve a certain version and then call that version\n+   directly.  */\n \n static void\n redirect_to_specific_clone (cgraph_node *node)\n {\n-  cgraph_function_version_info *fv = node->function_version ();\n-  if (fv == NULL)\n-    return;\n-\n-  gcc_assert (TARGET_HAS_FMV_TARGET_ATTRIBUTE);\n-  tree attr_target = lookup_attribute (\"target\", DECL_ATTRIBUTES (node->decl));\n-  if (attr_target == NULL_TREE)\n+  if (!targetm.compare_version_priority || !optimize)\n     return;\n \n   /* We need to remember NEXT_CALLER as it could be modified in the loop.  */\n   for (cgraph_edge *e = node->callees; e ; e = e->next_callee)\n     {\n-      cgraph_function_version_info *fv2 = e->callee->function_version ();\n-      if (!fv2)\n+      /* Only if this is a call to a dispatched symbol.  */\n+      if (!e->callee->dispatcher_function)\n \tcontinue;\n \n-      tree attr_target2 = lookup_attribute (\"target\",\n-\t\t\t\t\t    DECL_ATTRIBUTES (e->callee->decl));\n+      cgraph_function_version_info *callee_v\n+\t= e->callee->function_version ();\n+      cgraph_function_version_info *caller_v\n+\t= e->caller->function_version ();\n+\n+      gcc_assert (callee_v);\n \n-      /* Function is not calling proper target clone.  */\n-      if (attr_target2 == NULL_TREE\n-\t  || !attribute_value_equal (attr_target, attr_target2))\n+      /* Find the default nodes for both callee and caller (if present).  */\n+      cgraph_function_version_info *callee_default_v = callee_v->next;\n+      cgraph_function_version_info *caller_default_v = caller_v;\n+      if (caller_v)\n \t{\n-\t  while (fv2->prev != NULL)\n-\t    fv2 = fv2->prev;\n+\t  while (caller_default_v->prev)\n+\t    caller_default_v = caller_default_v->prev;\n+\t  if (!is_function_default_version (caller_default_v->this_node->decl))\n+\t    caller_default_v = NULL;\n+\t}\n+\n+      /* If this is not the TU that contains the definition of the default\n+\t version we are not guaranteed to have visibility of all versions\n+\t so cannot reason about them.  */\n+      if (!callee_default_v\n+\t  || !callee_default_v->this_node->binds_to_current_def_p ())\n+\tcontinue;\n+\n+      cgraph_function_version_info *highest_callable_fn = NULL;\n+      for (cgraph_function_version_info *ver = callee_v->next;\n+\t   ver;\n+\t   ver = ver->next)\n+\tif (targetm.target_option.functions_b_resolvable_from_a\n+\t      (node->decl, ver->this_node->decl, node->decl))\n+\t  highest_callable_fn = ver;\n \n-\t  /* Try to find a clone with equal target attribute.  */\n-\t  for (; fv2 != NULL; fv2 = fv2->next)\n+      if (!highest_callable_fn)\n+\tcontinue;\n+\n+      bool inlinable = true;\n+\n+      /* If there are higher priority versions of callee and caller has no\n+\t more version information, then not callable.  */\n+      if (highest_callable_fn->next)\n+\t{\n+\t  /* If this is not the decl where the callee default is defined then\n+\t     cannot reason about the caller versions.  */\n+\t  if (!caller_default_v\n+\t      || !caller_default_v->this_node->binds_to_current_def_p ())\n+\t    continue;\n+\n+\t  /* If every higher priority version would imply a higher priority\n+\t     version of caller would have been selected, then this is\n+\t     callable.  */\n+\t  for (cgraph_function_version_info *callee_ver\n+\t       = highest_callable_fn->next;\n+\t       callee_ver; callee_ver = callee_ver->next)\n \t    {\n-\t      cgraph_node *callee = fv2->this_node;\n-\t      attr_target2 = lookup_attribute (\"target\",\n-\t\t\t\t\t       DECL_ATTRIBUTES (callee->decl));\n-\t      if (attr_target2 != NULL_TREE\n-\t\t  && attribute_value_equal (attr_target, attr_target2))\n+\t      bool is_possible = true;\n+\t      for (cgraph_function_version_info *caller_ver = caller_v->next;\n+\t\t   caller_ver; caller_ver = caller_ver->next)\n+\t\tif (targetm.target_option.functions_b_resolvable_from_a\n+\t\t      (callee_ver->this_node->decl, caller_ver->this_node->decl,\n+\t\t       node->decl))\n+\t\t  {\n+\t\t    is_possible = false;\n+\t\t    break;\n+\t\t  }\n+\t      if (is_possible)\n \t\t{\n-\t\t  e->redirect_callee (callee);\n-\t\t  cgraph_edge::redirect_call_stmt_to_callee (e);\n+\t\t  inlinable = false;\n \t\t  break;\n \t\t}\n \t    }\n \t}\n+      if (inlinable)\n+\t{\n+\t  e->redirect_callee (highest_callable_fn->this_node);\n+\t  cgraph_edge::redirect_call_stmt_to_callee (e);\n+\t}\n     }\n }\n \n@@ -566,9 +605,8 @@ ipa_target_clone (bool early)\n   for (unsigned i = 0; i < to_dispatch.length (); i++)\n     create_dispatcher_calls (to_dispatch[i]);\n \n-  if (TARGET_HAS_FMV_TARGET_ATTRIBUTE)\n-    FOR_EACH_FUNCTION (node)\n-      redirect_to_specific_clone (node);\n+  FOR_EACH_FUNCTION (node)\n+    redirect_to_specific_clone (node);\n \n   return 0;\n }\ndiff --git a/gcc/target.def b/gcc/target.def\nindex 9c51c1989dd5..492ad6762748 100644\n--- a/gcc/target.def\n+++ b/gcc/target.def\n@@ -6934,6 +6934,28 @@ DEFHOOK\n  bool, (string_slice fn1, string_slice fn2),\n  hook_stringslice_stringslice_unreachable)\n \n+/* Checks if we can be certain that function DECL_A could resolve DECL_B.  */\n+DEFHOOK\n+(functions_b_resolvable_from_a,\n+ \"@var{decl_b} is a function declaration with a function multi-versioning\\n\\\n+(FMV) attribute; this attribute is either @code{target} or\\n\\\n+@code{target_version}, depending on @code{TARGET_HAS_FMV_TARGET_ATTRIBUTE}.\\n\\\n+@var{decl_a} is a function declaration that may or may not have an FMV\\n\\\n+attribute.\\n\\\n+\\n\\\n+Return true if we have enough information to determine that the\\n\\\n+requirements of @var{decl_b}'s FMV attribute are met whenever @var{decl_a}\\n\\\n+is executed, given that the target supports all features required by\\n\\\n+function declaration @var{base}.\\n\\\n+\\n\\\n+The default implementation just checks whether @var{decl_a} has the same\\n\\\n+FMV attribute as @var{decl_b}.  This is conservatively correct,\\n\\\n+but ports can do better by taking the relationships between architecture\\n\\\n+features into account.  For example, on AArch64, @code{sve} is present\\n\\\n+whenever @code{sve2} is present.\",\n+ bool, (tree decl_a, tree decl_v, tree base),\n+ functions_b_resolvable_from_a)\n+\n /* Function to determine if one function can inline another function.  */\n #undef HOOK_PREFIX\n #define HOOK_PREFIX \"TARGET_\"\ndiff --git a/gcc/testsuite/g++.target/aarch64/fmv-selection1.C b/gcc/testsuite/g++.target/aarch64/fmv-selection1.C\nnew file mode 100644\nindex 000000000000..4ee54466c133\n--- /dev/null\n+++ b/gcc/testsuite/g++.target/aarch64/fmv-selection1.C\n@@ -0,0 +1,40 @@\n+/* { dg-do compile } */\n+/* { dg-require-ifunc \"\" } */\n+/* { dg-options \"-O2 -march=armv8-a\" } */\n+\n+__attribute__((target_version(\"default\")))\n+__attribute__((optimize(\"O0\")))\n+int foo ()\n+{\n+  return 1;\n+}\n+\n+__attribute__((target_version(\"rng\")))\n+__attribute__((optimize(\"O0\")))\n+int foo ()\n+{\n+  return 2;\n+}\n+\n+__attribute__((target_version(\"flagm\")))\n+__attribute__((optimize(\"O0\")))\n+int foo ()\n+{\n+  return 3;\n+}\n+\n+__attribute__((target_version(\"rng+flagm\")))\n+__attribute__((optimize(\"O0\")))\n+int foo ()\n+{\n+  return 4;\n+}\n+\n+int bar()\n+{\n+  return foo ();\n+}\n+\n+/* Cannot optimize */\n+/* { dg-final { scan-assembler-times \"\\n\\tb\\t_Z3foov\\n\" 1 } } */\n+\ndiff --git a/gcc/testsuite/g++.target/aarch64/fmv-selection2.C b/gcc/testsuite/g++.target/aarch64/fmv-selection2.C\nnew file mode 100644\nindex 000000000000..f580dac4458a\n--- /dev/null\n+++ b/gcc/testsuite/g++.target/aarch64/fmv-selection2.C\n@@ -0,0 +1,40 @@\n+/* { dg-do compile } */\n+/* { dg-require-ifunc \"\" } */\n+/* { dg-options \"-O2 -march=armv8-a+rng+flagm\" } */\n+\n+__attribute__((target_version(\"default\")))\n+__attribute__((optimize(\"O0\")))\n+int foo ()\n+{\n+  return 1;\n+}\n+\n+__attribute__((target_version(\"rng\")))\n+__attribute__((optimize(\"O0\")))\n+int foo ()\n+{\n+  return 2;\n+}\n+\n+__attribute__((target_version(\"flagm\")))\n+__attribute__((optimize(\"O0\")))\n+int foo ()\n+{\n+  return 3;\n+}\n+\n+__attribute__((target_version(\"rng+flagm\")))\n+__attribute__((optimize(\"O0\")))\n+int foo ()\n+{\n+  return 4;\n+}\n+\n+int bar()\n+{\n+  return foo ();\n+}\n+\n+/* Can optimize to highest priority function */\n+/* { dg-final { scan-assembler-times \"\\n\\tb\\t_Z3foov\\._MrngMflagm\\n\" 1 } } */\n+\ndiff --git a/gcc/testsuite/g++.target/aarch64/fmv-selection3.C b/gcc/testsuite/g++.target/aarch64/fmv-selection3.C\nnew file mode 100644\nindex 000000000000..6b52fd4f644c\n--- /dev/null\n+++ b/gcc/testsuite/g++.target/aarch64/fmv-selection3.C\n@@ -0,0 +1,25 @@\n+/* { dg-do compile } */\n+/* { dg-require-ifunc \"\" } */\n+/* { dg-options \"-O2 -march=armv8-a\" } */\n+\n+__attribute__((target_version(\"default\")))\n+__attribute__((optimize(\"O0\")))\n+int foo ()\n+{ return 1; }\n+\n+__attribute__((target_version(\"rng\")))\n+int foo ();\n+__attribute__((target_version(\"flagm\")))\n+int foo ();\n+__attribute__((target_version(\"rng+flagm\")))\n+int foo ();\n+\n+__attribute__((target_version(\"rng+flagm\")))\n+int bar()\n+{\n+  return foo ();\n+}\n+\n+/* Cannot optimize */\n+/* { dg-final { scan-assembler-times \"\\n\\tb\\t_Z3foov\\._MrngMflagm\\n\" 1 } } */\n+\ndiff --git a/gcc/testsuite/g++.target/aarch64/fmv-selection4.C b/gcc/testsuite/g++.target/aarch64/fmv-selection4.C\nnew file mode 100644\nindex 000000000000..155145dcd885\n--- /dev/null\n+++ b/gcc/testsuite/g++.target/aarch64/fmv-selection4.C\n@@ -0,0 +1,30 @@\n+/* { dg-do compile } */\n+/* { dg-require-ifunc \"\" } */\n+/* { dg-options \"-O2 -march=armv8-a\" } */\n+\n+__attribute__((target_version(\"default\")))\n+__attribute__((optimize(\"O0\")))\n+int foo ()\n+{ return 1; }\n+\n+__attribute__((target_version(\"rng\")))\n+int foo ();\n+__attribute__((target_version(\"flagm\")))\n+int foo ();\n+__attribute__((target_version(\"rng+flagm\")))\n+int foo ();\n+\n+__attribute__((target_version(\"default\")))\n+int bar()\n+{\n+  return foo ();\n+}\n+\n+__attribute__((target_version(\"rng\")))\n+int bar();\n+\n+__attribute__((target_version(\"flagm\")))\n+int bar();\n+\n+/* { dg-final { scan-assembler-times \"\\n\\tb\\t_Z3foov\\.default\\n\" 1 } } */\n+\ndiff --git a/gcc/testsuite/g++.target/aarch64/fmv-selection5.C b/gcc/testsuite/g++.target/aarch64/fmv-selection5.C\nnew file mode 100644\nindex 000000000000..4d6d38e3754a\n--- /dev/null\n+++ b/gcc/testsuite/g++.target/aarch64/fmv-selection5.C\n@@ -0,0 +1,28 @@\n+/* { dg-do compile } */\n+/* { dg-require-ifunc \"\" } */\n+/* { dg-options \"-O2 -march=armv8-a\" } */\n+\n+__attribute__((target_version(\"default\")))\n+__attribute__((optimize(\"O0\")))\n+int foo ()\n+{ return 1; }\n+\n+__attribute__((target_version(\"rng\")))\n+int foo ();\n+__attribute__((target_version(\"flagm\")))\n+int foo ();\n+__attribute__((target_version(\"rng+flagm\")))\n+int foo ();\n+\n+__attribute__((target_version(\"default\")))\n+int bar()\n+{\n+  return foo ();\n+}\n+\n+__attribute__((target_version(\"flagm\")))\n+int bar();\n+\n+/* { dg-final { scan-assembler-times \"\\n\\tb\\t_Z3foov\\.default\\n\" 0 } } */\n+/* { dg-final { scan-assembler-times \"\\n\\tb\\t_Z3foov\\n\" 1 } } */\n+\ndiff --git a/gcc/testsuite/g++.target/aarch64/fmv-selection6.C b/gcc/testsuite/g++.target/aarch64/fmv-selection6.C\nnew file mode 100644\nindex 000000000000..db384e16c099\n--- /dev/null\n+++ b/gcc/testsuite/g++.target/aarch64/fmv-selection6.C\n@@ -0,0 +1,27 @@\n+/* { dg-do compile } */\n+/* { dg-require-ifunc \"\" } */\n+/* { dg-options \"-O2 -march=armv8-a+rng\" } */\n+\n+__attribute__((target_version(\"default\")))\n+__attribute__((optimize(\"O0\")))\n+int foo ()\n+{ return 1; }\n+\n+__attribute__((target_version(\"rng\")))\n+int foo ();\n+__attribute__((target_version(\"flagm\")))\n+int foo ();\n+__attribute__((target_version(\"rng+flagm\")))\n+int foo ();\n+\n+__attribute__((target_version(\"default\")))\n+int bar()\n+{\n+  return foo ();\n+}\n+\n+__attribute__((target_version(\"flagm\")))\n+int bar();\n+\n+/* { dg-final { scan-assembler-times \"\\n\\tb\\t_Z3foov\\._Mrng\\n\" 1 } } */\n+\ndiff --git a/gcc/testsuite/g++.target/aarch64/fmv-selection7.C b/gcc/testsuite/g++.target/aarch64/fmv-selection7.C\nnew file mode 100644\nindex 000000000000..41e7462ebb10\n--- /dev/null\n+++ b/gcc/testsuite/g++.target/aarch64/fmv-selection7.C\n@@ -0,0 +1,65 @@\n+/* { dg-do compile } */\n+/* { dg-require-ifunc \"\" } */\n+/* { dg-options \"-O2 -march=armv8-a\" } */\n+\n+[[gnu::optimize(\"O0\")]]\n+[[gnu::target_version (\"default\")]]\n+int bar () {\n+  return 1;\n+}\n+\n+[[gnu::optimize(\"O0\")]]\n+[[gnu::target (\"+sve2\")]]\n+[[gnu::target_version (\"sve\")]]\n+int bar ();\n+\n+[[gnu::target (\"+sve\")]]\n+int foo () {\n+  return bar();\n+}\n+\n+/* { dg-final { scan-assembler-times \"\\n\\tb\\t_Z3barv\\._Msve\\n\" 1 } } */\n+\n+[[gnu::target_version (\"default\")]]\n+int bar2 () {\n+  return 1;\n+}\n+\n+[[gnu::target_version (\"sve2\")]]\n+int bar2 ();\n+\n+[[gnu::target_version (\"default\")]]\n+int foo2 ();\n+\n+[[gnu::target_version (\"sve\")]]\n+[[gnu::target (\"+sve2\")]]\n+int foo2 () {\n+  return bar2();\n+}\n+\n+/* { dg-final { scan-assembler-times \"\\n\\tb\\t_Z4bar2v\\._Msve2\\n\" 1 } } */\n+\n+[[gnu::target_version (\"default\")]]\n+int bar3 () {\n+  return 1;\n+}\n+\n+[[gnu::target_version (\"sve\")]]\n+int bar3 ();\n+\n+[[gnu::target (\"+rng\")]]\n+[[gnu::target_version (\"sve2\")]]\n+int bar3 ();\n+\n+[[gnu::target_version (\"default\")]]\n+int foo3 ();\n+\n+[[gnu::target_version (\"sve\")]]\n+int foo3 () {\n+  return bar3 ();\n+}\n+\n+[[gnu::target_version (\"sve2+rng\")]]\n+int foo3 ();\n+\n+/* { dg-final { scan-assembler-times \"\\n\\tb\\t_Z4bar3v\\n\" 1 } } */\n",
    "prefixes": [
        "v1",
        "13/13"
    ]
}