Ping for the front-end agnostic parts of this patch. 
(attribs.h/cc, hooks.h/cc, tree.h/cc, docs)

All other parts have been approved.

Thanks,
Alfie

The 08/28/2025 09:49, alfie.richa...@arm.com wrote:
> From: Alfie Richards <alfie.richa...@arm.com>
> 
> This change refactors FMV handling in the frontend to allows greater
> reasoning about versions in shared code.
> 
> This is needed for allowing target_clones and target_versions to be used
> together in a function set, as there is then two distinct concerns when
> encountering two declarations that previously were conflated:
> 
> 1. Are these two declarations completely disjoint FMV declarations
> (ie. the sets of versions they define have no overlap). If so, they don't
> conflict so there is no need to merge and both can be pushed.
> 2. For two declarations that aren't completely dijoint, are they matching
> and therefore mergeable. (ie. two target_clone decls that define the same set
> of versions, or an un-annotated declaration, and a target_clones definition
> containing the default version). If so, continue to the existing merging logic
> to try to merge these and diagnose if it's not possible.
> If not, then diagnose the confliciting declarations.
> 
> To do this the common_function_versions function has been renamed
> disjoint_function_versions (meaning, are the version sets defined by these
> two decl's completely distinct from eachother).
> 
> A new hook called same_function_version is introduces taking two
> string_slice's (each representing a single version) and determining if they
> define the same version.
> 
> A new function, called diagnose_versioned_decls is added, which checks
> if two decls (with overlapping version sets) can be merged and diagnose when
> they cannot be (only in terms of the attributes, the existing logic is used to
> detect other mergability conflicts like redefinition).
> 
> This only effects targets with TARGET_HAS_FMV_TARGET_ATTRIBUTE set to false.
> (ie. aarch64 and riscv), the existing logic for i86 and ppc is unchanged.
> This also means the same function version hook is only used for aarch64 and
> riscv.
> 
> gcc/ChangeLog:
> 
>       * attribs.h (common_function_versions): Removed.
>       * attribs.cc (common_function_versions): Removed.
>       * config/aarch64/aarch64.cc (aarch64_common_function_versions): Removed.
>       (aarch64_same_function_versions): New function to check if two version
>       strings imply the same version.
>       (TARGET_OPTION_FUNCTION_VERSIONS): Removed.
>       (TARGET_OPTION_SAME_FUNCTION_VERSIONS): New macro.
>       * config/i386/i386.cc (TARGET_OPTION_FUNCTION_VERSIONS): Removed.
>       * config/rs6000/rs6000.cc (TARGET_OPTION_FUNCTION_VERSIONS): Removed.
>       * config/riscv/riscv.cc (riscv_same_function_versions):  New function
>       to check if two version strings imply the same version.
>       (riscv_common_function_versions): Removed.
>       (TARGET_OPTION_FUNCTION_VERSIONS): Removed.
>       (TARGET_OPTION_SAME_FUNCTION_VERSIONS): New macro.
>       * doc/tm.texi: Regenerated.
>       * target.def: Remove common_version hook and add same_function_version
>       hook.
>       * doc/tm.texi.in: Ditto.
>       * tree.cc (distinct_version_decls): New function.
>       (mergeable_version_decls): Ditto.
>       * tree.h (distinct_version_decls): New function.
>       (mergeable_version_decls): Ditto.
>       * hooks.h (hook_stringslice_stringslice_unreachable): New function.
>       * hooks.cc (hook_stringslice_stringslice_unreachable): New function.
> 
> gcc/cp/ChangeLog:
> 
>       * class.cc (resolve_address_of_overloaded_function): Updated to use
>       dijoint_versions_decls instead of common_function_version hook.
>       * decl.cc (decls_match): Refacture to use disjoint_version_decls and
>       to pass through conflicting_version argument.
>       (maybe_version_functions): Updated to use
>       disjoint_version_decls instead of common_function_version hook.
>       (duplicate_decls): Add logic to handle conflicting unmergable decls
>       and improve diagnostics for conflicting versions.
>       * decl2.cc (check_classfn): Updated to use
>       disjoint_version_decls instead of common_function_version hook.
> ---
>  gcc/attribs.cc                |  67 ----------
>  gcc/attribs.h                 |   1 -
>  gcc/config/aarch64/aarch64.cc |  25 ++--
>  gcc/config/i386/i386.cc       |   3 -
>  gcc/config/riscv/riscv.cc     |  35 ++---
>  gcc/config/rs6000/rs6000.cc   |   3 -
>  gcc/cp/class.cc               |   3 +-
>  gcc/cp/decl.cc                |   8 +-
>  gcc/cp/decl2.cc               |   2 +-
>  gcc/doc/tm.texi               |   9 +-
>  gcc/doc/tm.texi.in            |   2 +-
>  gcc/hooks.cc                  |   7 +
>  gcc/hooks.h                   |   1 +
>  gcc/target.def                |  21 ++-
>  gcc/tree.cc                   | 243 ++++++++++++++++++++++++++++++++++
>  gcc/tree.h                    |   5 +
>  16 files changed, 310 insertions(+), 125 deletions(-)
> 
> diff --git a/gcc/attribs.cc b/gcc/attribs.cc
> index c75fd1371fd..9efc327553f 100644
> --- a/gcc/attribs.cc
> +++ b/gcc/attribs.cc
> @@ -1086,8 +1086,6 @@ make_attribute (string_slice name, string_slice 
> arg_name, tree chain)
>    return attr;
>  }
>  
> -/* Common functions used for target clone support.  */
> -
>  /* Comparator function to be used in qsort routine to sort attribute
>     specification strings to "target".  */
>  
> @@ -1177,71 +1175,6 @@ sorted_attr_string (tree arglist)
>    return ret_str;
>  }
>  
> -
> -/* This function returns true if FN1 and FN2 are versions of the same 
> function,
> -   that is, the target strings of the function decls are different.  This 
> assumes
> -   that FN1 and FN2 have the same signature.  */
> -
> -bool
> -common_function_versions (tree fn1, tree fn2)
> -{
> -  tree attr1, attr2;
> -  char *target1, *target2;
> -  bool result;
> -
> -  if (TREE_CODE (fn1) != FUNCTION_DECL
> -      || TREE_CODE (fn2) != FUNCTION_DECL)
> -    return false;
> -
> -  attr1 = lookup_attribute ("target", DECL_ATTRIBUTES (fn1));
> -  attr2 = lookup_attribute ("target", DECL_ATTRIBUTES (fn2));
> -
> -  /* At least one function decl should have the target attribute specified.  
> */
> -  if (attr1 == NULL_TREE && attr2 == NULL_TREE)
> -    return false;
> -
> -  /* Diagnose missing target attribute if one of the decls is already
> -     multi-versioned.  */
> -  if (attr1 == NULL_TREE || attr2 == NULL_TREE)
> -    {
> -      if (DECL_FUNCTION_VERSIONED (fn1) || DECL_FUNCTION_VERSIONED (fn2))
> -     {
> -       if (attr2 != NULL_TREE)
> -         {
> -           std::swap (fn1, fn2);
> -           attr1 = attr2;
> -         }
> -       auto_diagnostic_group d;
> -       error_at (DECL_SOURCE_LOCATION (fn2),
> -                 "missing %<target%> attribute for multi-versioned %qD",
> -                 fn2);
> -       inform (DECL_SOURCE_LOCATION (fn1),
> -               "previous declaration of %qD", fn1);
> -       /* Prevent diagnosing of the same error multiple times.  */
> -       DECL_ATTRIBUTES (fn2)
> -         = tree_cons (get_identifier ("target"),
> -                      copy_node (TREE_VALUE (attr1)),
> -                      DECL_ATTRIBUTES (fn2));
> -     }
> -      return false;
> -    }
> -
> -  target1 = sorted_attr_string (TREE_VALUE (attr1));
> -  target2 = sorted_attr_string (TREE_VALUE (attr2));
> -
> -  /* The sorted target strings must be different for fn1 and fn2
> -     to be versions.  */
> -  if (strcmp (target1, target2) == 0)
> -    result = false;
> -  else
> -    result = true;
> -
> -  XDELETEVEC (target1);
> -  XDELETEVEC (target2);
> -
> -  return result;
> -}
> -
>  /* Make a dispatcher declaration for the multi-versioned function DECL.
>     Calls to DECL function will be replaced with calls to the dispatcher
>     by the front-end.  Return the decl created.  */
> diff --git a/gcc/attribs.h b/gcc/attribs.h
> index b8b6838599c..c4a4fb0e50b 100644
> --- a/gcc/attribs.h
> +++ b/gcc/attribs.h
> @@ -54,7 +54,6 @@ extern struct scoped_attributes *
>    register_scoped_attributes (const scoped_attribute_specs &, bool = false);
>  
>  extern char *sorted_attr_string (tree);
> -extern bool common_function_versions (tree, tree);
>  extern tree make_dispatcher_decl (const tree);
>  extern bool is_function_default_version (const tree);
>  extern void handle_ignored_attributes_option (vec<char *> *);
> diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
> index 766da917732..d89c9df3df2 100644
> --- a/gcc/config/aarch64/aarch64.cc
> +++ b/gcc/config/aarch64/aarch64.cc
> @@ -21161,18 +21161,23 @@ aarch64_get_function_versions_dispatcher (void 
> *decl)
>    return dispatch_decl;
>  }
>  
> -/* This function returns true if FN1 and FN2 are versions of the same 
> function,
> -   that is, the target_version attributes of the function decls are 
> different.
> -   This assumes that FN1 and FN2 have the same signature.  */
> +/* This function returns true if STR1 and STR2 are version strings for the 
> same
> +   function.  */
>  
>  bool
> -aarch64_common_function_versions (tree fn1, tree fn2)
> +aarch64_same_function_versions (string_slice str1, string_slice str2)
>  {
> -  if (TREE_CODE (fn1) != FUNCTION_DECL
> -      || TREE_CODE (fn2) != FUNCTION_DECL)
> -    return false;
> +  enum aarch_parse_opt_result parse_res;
> +  aarch64_fmv_feature_mask feature_mask1;
> +  aarch64_fmv_feature_mask feature_mask2;
> +  parse_res = aarch64_parse_fmv_features (str1, NULL,
> +                                       &feature_mask1, NULL);
> +  gcc_assert (parse_res == AARCH_PARSE_OK);
> +  parse_res = aarch64_parse_fmv_features (str2, NULL,
> +                                       &feature_mask2, NULL);
> +  gcc_assert (parse_res == AARCH_PARSE_OK);
>  
> -  return (aarch64_compare_version_priority (fn1, fn2) != 0);
> +  return feature_mask1 == feature_mask2;
>  }
>  
>  /* Implement TARGET_FUNCTION_ATTRIBUTE_INLINABLE_P.  Use an opt-out
> @@ -32837,8 +32842,8 @@ aarch64_libgcc_floating_mode_supported_p
>  #undef TARGET_EMIT_EPILOGUE_FOR_SIBCALL
>  #define TARGET_EMIT_EPILOGUE_FOR_SIBCALL aarch64_expand_epilogue
>  
> -#undef TARGET_OPTION_FUNCTION_VERSIONS
> -#define TARGET_OPTION_FUNCTION_VERSIONS aarch64_common_function_versions
> +#undef TARGET_OPTION_SAME_FUNCTION_VERSIONS
> +#define TARGET_OPTION_SAME_FUNCTION_VERSIONS aarch64_same_function_versions
>  
>  #undef TARGET_CHECK_TARGET_CLONE_VERSION
>  #define TARGET_CHECK_TARGET_CLONE_VERSION aarch64_check_target_clone_version
> diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
> index 1ca6c612137..273ad312a54 100644
> --- a/gcc/config/i386/i386.cc
> +++ b/gcc/config/i386/i386.cc
> @@ -28163,9 +28163,6 @@ ix86_libgcc_floating_mode_supported_p
>  #undef TARGET_OPTION_PRINT
>  #define TARGET_OPTION_PRINT ix86_function_specific_print
>  
> -#undef TARGET_OPTION_FUNCTION_VERSIONS
> -#define TARGET_OPTION_FUNCTION_VERSIONS common_function_versions
> -
>  #undef TARGET_CAN_INLINE_P
>  #define TARGET_CAN_INLINE_P ix86_can_inline_p
>  
> diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
> index e01aa37f947..e45dc0e68f5 100644
> --- a/gcc/config/riscv/riscv.cc
> +++ b/gcc/config/riscv/riscv.cc
> @@ -14044,6 +14044,23 @@ compare_fmv_features (const struct 
> riscv_feature_bits &mask1,
>    return 0;
>  }
>  
> +/* This function returns true if V1 and V2 specify the same function
> +   version.  */
> +
> +bool
> +riscv_same_function_versions (string_slice v1, string_slice v2)
> +{
> +  struct riscv_feature_bits mask1, mask2;
> +  int prio1, prio2;
> +
> +  /* Invalid features should have already been rejected by this point so
> +     providing no location should be okay.  */
> +  parse_features_for_version (v1, UNKNOWN_LOCATION, mask1, prio1);
> +  parse_features_for_version (v2, UNKNOWN_LOCATION, mask2, prio2);
> +
> +  return compare_fmv_features (mask1, mask2, prio1, prio2) == 0;
> +}
> +
>  /* Compare priorities of two version decls.  Return:
>       1: mask1 is higher priority
>      -1: mask2 is higher priority
> @@ -14068,20 +14085,6 @@ riscv_compare_version_priority (tree decl1, tree 
> decl2)
>    return compare_fmv_features (mask1, mask2, prio1, prio2);
>  }
>  
> -/* This function returns true if FN1 and FN2 are versions of the same 
> function,
> -   that is, the target_version attributes of the function decls are 
> different.
> -   This assumes that FN1 and FN2 have the same signature.  */
> -
> -bool
> -riscv_common_function_versions (tree fn1, tree fn2)
> -{
> -  if (TREE_CODE (fn1) != FUNCTION_DECL
> -      || TREE_CODE (fn2) != FUNCTION_DECL)
> -    return false;
> -
> -  return riscv_compare_version_priority (fn1, fn2) != 0;
> -}
> -
>  /* Checks if the function version specifying string STR parses correctly.
>     If it is an invalid string, currently emits a diagnostic at LOC.
>     Always returns true.  */
> @@ -15854,8 +15857,8 @@ riscv_prefetch_offset_address_p (rtx x, machine_mode 
> mode)
>  #undef TARGET_CHECK_TARGET_CLONE_VERSION
>  #define TARGET_CHECK_TARGET_CLONE_VERSION riscv_check_target_clone_version
>  
> -#undef TARGET_OPTION_FUNCTION_VERSIONS
> -#define TARGET_OPTION_FUNCTION_VERSIONS riscv_common_function_versions
> +#undef TARGET_OPTION_SAME_FUNCTION_VERSIONS
> +#define TARGET_OPTION_SAME_FUNCTION_VERSIONS riscv_same_function_versions
>  
>  #undef TARGET_MANGLE_DECL_ASSEMBLER_NAME
>  #define TARGET_MANGLE_DECL_ASSEMBLER_NAME riscv_mangle_decl_assembler_name
> diff --git a/gcc/config/rs6000/rs6000.cc b/gcc/config/rs6000/rs6000.cc
> index d00d5f27121..1049c446c40 100644
> --- a/gcc/config/rs6000/rs6000.cc
> +++ b/gcc/config/rs6000/rs6000.cc
> @@ -1730,9 +1730,6 @@ static const scoped_attribute_specs *const 
> rs6000_attribute_table[] =
>  #define TARGET_GET_FUNCTION_VERSIONS_DISPATCHER                              
> \
>    rs6000_get_function_versions_dispatcher
>  
> -#undef TARGET_OPTION_FUNCTION_VERSIONS
> -#define TARGET_OPTION_FUNCTION_VERSIONS common_function_versions
> -
>  #undef TARGET_HARD_REGNO_NREGS
>  #define TARGET_HARD_REGNO_NREGS rs6000_hard_regno_nregs_hook
>  #undef TARGET_HARD_REGNO_MODE_OK
> diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc
> index df8d3c9321e..b56cc518a11 100644
> --- a/gcc/cp/class.cc
> +++ b/gcc/cp/class.cc
> @@ -9127,8 +9127,7 @@ resolve_address_of_overloaded_function (tree 
> target_type,
>        decls_match will return false as they are different.  */
>        for (match = TREE_CHAIN (matches); match; match = TREE_CHAIN (match))
>       if (!decls_match (fn, TREE_PURPOSE (match))
> -         && !targetm.target_option.function_versions
> -            (fn, TREE_PURPOSE (match)))
> +         && !disjoint_version_decls (fn, TREE_PURPOSE (match)))
>            break;
>  
>        if (match)
> diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
> index 5dd5d767c19..616498e23ea 100644
> --- a/gcc/cp/decl.cc
> +++ b/gcc/cp/decl.cc
> @@ -1211,7 +1211,7 @@ decls_match (tree newdecl, tree olddecl, bool 
> record_versions /* = true */)
>        if (types_match
>         && !DECL_EXTERN_C_P (newdecl)
>         && !DECL_EXTERN_C_P (olddecl)
> -       && targetm.target_option.function_versions (newdecl, olddecl))
> +       && disjoint_version_decls (newdecl, olddecl))
>       {
>         if (record_versions)
>           maybe_version_functions (newdecl, olddecl);
> @@ -1294,7 +1294,7 @@ maybe_mark_function_versioned (tree decl)
>  bool
>  maybe_version_functions (tree newdecl, tree olddecl)
>  {
> -  if (!targetm.target_option.function_versions (newdecl, olddecl))
> +  if (!disjoint_version_decls (newdecl, olddecl))
>      return false;
>  
>    maybe_mark_function_versioned (olddecl);
> @@ -2106,6 +2106,10 @@ duplicate_decls (tree newdecl, tree olddecl, bool 
> hiding, bool was_hidden)
>        /* Leave it to update_binding to merge or report error.  */
>        return NULL_TREE;
>      }
> +  /* Check if the two decls are non-mergeable versioned decls.  */
> +  else if (!TARGET_HAS_FMV_TARGET_ATTRIBUTE
> +        && diagnose_versioned_decls (olddecl, newdecl))
> +    return error_mark_node;
>    else
>      {
>        const char *errmsg = redeclaration_error_message (newdecl, olddecl);
> diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
> index c6c9dfc7723..7e5225cf03e 100644
> --- a/gcc/cp/decl2.cc
> +++ b/gcc/cp/decl2.cc
> @@ -876,7 +876,7 @@ check_classfn (tree ctype, tree function, tree 
> template_parms)
>        if (same_type_p (TREE_TYPE (TREE_TYPE (function)),
>                      TREE_TYPE (TREE_TYPE (fndecl)))
>         && compparms (p1, p2)
> -       && !targetm.target_option.function_versions (function, fndecl)
> +       && !disjoint_version_decls (function, fndecl)
>         && (!is_template
>             || comp_template_parms (template_parms,
>                                     DECL_TEMPLATE_PARMS (fndecl)))
> diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
> index 4650e805846..65e2ce8ad42 100644
> --- a/gcc/doc/tm.texi
> +++ b/gcc/doc/tm.texi
> @@ -10977,12 +10977,9 @@ changed via the optimize attribute or pragma, see
>  @code{TARGET_OVERRIDE_OPTIONS_AFTER_CHANGE}
>  @end deftypefn
>  
> -@deftypefn {Target Hook} bool TARGET_OPTION_FUNCTION_VERSIONS (tree 
> @var{decl1}, tree @var{decl2})
> -This target hook returns @code{true} if @var{DECL1} and @var{DECL2} are
> -versions of the same function.  @var{DECL1} and @var{DECL2} are function
> -versions if and only if they have the same function signature and
> -different target specific attributes, that is, they are compiled for
> -different target machines.
> +@deftypefn {Target Hook} bool TARGET_OPTION_SAME_FUNCTION_VERSIONS 
> (string_slice @var{fn1}, string_slice @var{fn2})
> +This target hook returns @code{true} if the target/target-version strings
> +@var{fn1} and @var{fn2} imply the same function version.
>  @end deftypefn
>  
>  @deftypefn {Target Hook} bool TARGET_CAN_INLINE_P (tree @var{caller}, tree 
> @var{callee})
> diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
> index 30a9400f0f9..57653a133a6 100644
> --- a/gcc/doc/tm.texi.in
> +++ b/gcc/doc/tm.texi.in
> @@ -7140,7 +7140,7 @@ with the target specific attributes.  The default value 
> is @code{','}.
>  
>  @hook TARGET_OPTION_OVERRIDE
>  
> -@hook TARGET_OPTION_FUNCTION_VERSIONS
> +@hook TARGET_OPTION_SAME_FUNCTION_VERSIONS
>  
>  @hook TARGET_CAN_INLINE_P
>  
> diff --git a/gcc/hooks.cc b/gcc/hooks.cc
> index 865820d80b1..820c81e31e4 100644
> --- a/gcc/hooks.cc
> +++ b/gcc/hooks.cc
> @@ -27,6 +27,7 @@
>  #include "coretypes.h"
>  #include "tm.h"
>  #include "hooks.h"
> +#include "vec.h"
>  
>  /* Generic hook that does absolutely zappo.  */
>  void
> @@ -593,3 +594,9 @@ hook_stringslice_locationtptr_true (string_slice, 
> location_t *)
>  {
>    return true;
>  }
> +
> +bool
> +hook_stringslice_stringslice_unreachable (string_slice, string_slice)
> +{
> +  gcc_unreachable ();
> +}
> diff --git a/gcc/hooks.h b/gcc/hooks.h
> index 08542d7a24e..a7021f532a5 100644
> --- a/gcc/hooks.h
> +++ b/gcc/hooks.h
> @@ -139,5 +139,6 @@ extern opt_machine_mode hook_optmode_mode_uhwi_none 
> (machine_mode,
>                                                    unsigned HOST_WIDE_INT);
>  
>  extern bool hook_stringslice_locationtptr_true (string_slice, location_t *);
> +extern bool hook_stringslice_stringslice_unreachable (string_slice, 
> string_slice);
>  
>  #endif
> diff --git a/gcc/target.def b/gcc/target.def
> index adb8edc7353..1b95525a902 100644
> --- a/gcc/target.def
> +++ b/gcc/target.def
> @@ -6925,19 +6925,14 @@ changed via the optimize attribute or pragma, see\n\
>   void, (void),
>   hook_void_void)
>  
> -/* This function returns true if DECL1 and DECL2 are versions of the same
> -   function.  DECL1 and DECL2 are function versions if and only if they
> -   have the same function signature and different target specific attributes,
> -   that is, they are compiled for different target machines.  */
> -DEFHOOK
> -(function_versions,
> - "This target hook returns @code{true} if @var{DECL1} and @var{DECL2} are\n\
> -versions of the same function.  @var{DECL1} and @var{DECL2} are function\n\
> -versions if and only if they have the same function signature and\n\
> -different target specific attributes, that is, they are compiled for\n\
> -different target machines.",
> - bool, (tree decl1, tree decl2),
> - hook_bool_tree_tree_false)
> +/* This function returns true if FN1 and FN2 define the same version of a
> +   function.  */
> +DEFHOOK
> +(same_function_versions,
> + "This target hook returns @code{true} if the target/target-version 
> strings\n\
> +@var{fn1} and @var{fn2} imply the same function version.",
> + bool, (string_slice fn1, string_slice fn2),
> + hook_stringslice_stringslice_unreachable)
>  
>  /* Function to determine if one function can inline another function.  */
>  #undef HOOK_PREFIX
> diff --git a/gcc/tree.cc b/gcc/tree.cc
> index 05fd3d097e0..e3fc7f4fe58 100644
> --- a/gcc/tree.cc
> +++ b/gcc/tree.cc
> @@ -15486,6 +15486,249 @@ get_target_version (const tree decl)
>          .strip ();
>  }
>  
> +/* Returns true if FN1 and FN2 define dijoint function versions in an FMV
> +   function set.  That is, the two declarations are completely 
> non-overlapping.
> +   For target_version semantics, that means if one is a target clone and one 
> is
> +   a target version, the target_version must not be defined by the 
> target_clone,
> +   and for two target_clones, they must not define any of the same version.
> +
> +   FN1 and FN2 should be function decls.  */
> +
> +bool
> +disjoint_version_decls (tree fn1, tree fn2)
> +{
> +  if (TREE_CODE (fn1) != FUNCTION_DECL
> +      || TREE_CODE (fn2) != FUNCTION_DECL)
> +    return false;
> +
> +  if (TARGET_HAS_FMV_TARGET_ATTRIBUTE)
> +    {
> +      tree attr1 = lookup_attribute ("target", DECL_ATTRIBUTES (fn1));
> +      tree attr2 = lookup_attribute ("target", DECL_ATTRIBUTES (fn2));
> +
> +      /* At least one function decl should have the target attribute
> +      specified.  */
> +      if (attr1 == NULL_TREE && attr2 == NULL_TREE)
> +     return false;
> +
> +      /* Diagnose missing target attribute if one of the decls is already
> +      multi-versioned.  */
> +      if (attr1 == NULL_TREE || attr2 == NULL_TREE)
> +     {
> +       if (DECL_FUNCTION_VERSIONED (fn1) || DECL_FUNCTION_VERSIONED (fn2))
> +         {
> +           if (attr2 != NULL_TREE)
> +             {
> +               std::swap (fn1, fn2);
> +               attr1 = attr2;
> +             }
> +           auto_diagnostic_group d;
> +           error_at (DECL_SOURCE_LOCATION (fn2),
> +                     "missing %<target%> attribute for multi-versioned %qD",
> +                     fn2);
> +           inform (DECL_SOURCE_LOCATION (fn1),
> +                   "previous declaration of %qD", fn1);
> +           /* Prevent diagnosing of the same error multiple times.  */
> +           DECL_ATTRIBUTES (fn2)
> +             = tree_cons (get_identifier ("target"),
> +                          copy_node (TREE_VALUE (attr1)),
> +                          DECL_ATTRIBUTES (fn2));
> +         }
> +       return false;
> +     }
> +
> +      char *target1 = sorted_attr_string (TREE_VALUE (attr1));
> +      char *target2 = sorted_attr_string (TREE_VALUE (attr2));
> +
> +      /* The sorted target strings must be different for fn1 and fn2
> +      to be versions.  */
> +      bool result = strcmp (target1, target2) != 0;
> +
> +      XDELETEVEC (target1);
> +      XDELETEVEC (target2);
> +
> +      return result;
> +    }
> +  else
> +    {
> +      /* As this is symmetric, can remove the case where fn2 is target clone
> +      and fn1 is target version by swapping here.  */
> +      if (lookup_attribute ("target_clones", DECL_ATTRIBUTES (fn2)))
> +     std::swap (fn1, fn2);
> +
> +      if (lookup_attribute ("target_clones", DECL_ATTRIBUTES (fn1)))
> +     {
> +       auto_vec<string_slice> fn1_versions = get_clone_versions (fn1);
> +       /* fn1 is target_clone.  */
> +       if (lookup_attribute ("target_clones", DECL_ATTRIBUTES (fn2)))
> +         {
> +           /* Both are target_clone.  */
> +           auto_vec<string_slice> fn2_versions = get_clone_versions (fn2);
> +           for (string_slice v1 : fn1_versions)
> +             {
> +               for (string_slice v2 : fn2_versions)
> +                 if (targetm.target_option.same_function_versions (v1, v2))
> +                   return false;
> +             }
> +           return true;
> +         }
> +       else
> +         {
> +           string_slice v2 = get_target_version (fn2);
> +
> +           /* target and target_clones is always conflicting for target
> +              semantics.  */
> +           if (TARGET_HAS_FMV_TARGET_ATTRIBUTE)
> +             return false;
> +
> +           /* Only fn1 is target clone.  */
> +           if (!v2.is_valid ())
> +             v2 = "default";
> +           for (string_slice v1 : fn1_versions)
> +             if (targetm.target_option.same_function_versions (v1, v2))
> +               return false;
> +           return true;
> +         }
> +     }
> +      else
> +     {
> +       /* Both are target_version.  */
> +       string_slice v1 = get_target_version (fn1);
> +       string_slice v2 = get_target_version (fn2);
> +
> +       if (!v1.is_valid () && !v2.is_valid ())
> +         return false;
> +
> +       if (!v1.is_valid ())
> +         v1 = "default";
> +       if (!v2.is_valid ())
> +         v2 = "default";
> +
> +       if (targetm.target_option.same_function_versions (v1, v2))
> +         return false;
> +
> +       return true;
> +     }
> +    }
> +}
> +
> +/* Check if the target_version/target_clones attributes are mergeable
> +   for two decls, and if so returns false.
> +   If they aren't mergeable, diagnose this and return true.
> +   Only works for target_version semantics.  */
> +bool
> +diagnose_versioned_decls (tree old_decl, tree new_decl)
> +{
> +  gcc_assert (!TARGET_HAS_FMV_TARGET_ATTRIBUTE);
> +
> +  string_slice old_target_attr = get_target_version (old_decl);
> +  string_slice new_target_attr = get_target_version (new_decl);
> +
> +  tree old_target_clones_attr = lookup_attribute ("target_clones",
> +                                               DECL_ATTRIBUTES (old_decl));
> +  tree new_target_clones_attr = lookup_attribute ("target_clones",
> +                                               DECL_ATTRIBUTES (new_decl));
> +
> +  /* If none of these are annotated, then it is mergeable.  */
> +  if (!old_target_attr.is_valid ()
> +      && !old_target_attr.is_valid ()
> +      && !old_target_clones_attr
> +      && !new_target_clones_attr)
> +    return false;
> +
> +  /* If fn1 is unnanotated and fn2 contains default, then is mergeable.  */
> +  if (!old_target_attr.is_valid ()
> +      && !old_target_clones_attr
> +      && is_function_default_version (new_decl))
> +    return false;
> +
> +  /* If fn2 is unnanotated and fn1 contains default, then is mergeable.  */
> +  if (!new_target_attr.is_valid ()
> +      && !new_target_clones_attr
> +      && is_function_default_version (old_decl))
> +    return false;
> +
> +  /* In the case where both are annotated with target_clones, only mergeable 
> if
> +     the two sets of target_clones imply the same set of versions.  */
> +  if (old_target_clones_attr && new_target_clones_attr)
> +    {
> +      auto_vec<string_slice> fn1_versions = get_clone_versions (old_decl);
> +      auto_vec<string_slice> fn2_versions = get_clone_versions (new_decl);
> +
> +      bool mergeable = true;
> +
> +      if (fn1_versions.length () != fn2_versions.length ())
> +     mergeable = false;
> +
> +      /* Check both inclusion directions.  */
> +      for (auto fn1v : fn1_versions)
> +     {
> +       bool matched = false;
> +       for (auto fn2v : fn2_versions)
> +         if (targetm.target_option.same_function_versions (fn1v, fn2v))
> +           matched = true;
> +       if (!matched)
> +         mergeable = false;
> +     }
> +
> +      for (auto fn2v : fn2_versions)
> +     {
> +       bool matched = false;
> +       for (auto fn1v : fn1_versions)
> +         if (targetm.target_option.same_function_versions (fn1v, fn2v))
> +           matched = true;
> +       if (!matched)
> +         mergeable = false;
> +     }
> +
> +      if (!mergeable)
> +     {
> +       error_at (DECL_SOURCE_LOCATION (new_decl),
> +                 "%qD conflicts with overlapping %<target_clone%> "
> +                 "declaration",
> +                 new_decl);
> +       inform (DECL_SOURCE_LOCATION (old_decl),
> +               "previous declaration of %qD", old_decl);
> +       return true;
> +     }
> +
> +      return false;
> +    }
> +
> +  /* If olddecl is target clones and newdecl is a target_version.
> +     As they are not distinct this implies newdecl redefines a version of
> +     olddecl.  Not mergeable.  */
> +  if (new_target_clones_attr)
> +    {
> +      gcc_assert (old_target_attr.is_valid ());
> +
> +      error_at (DECL_SOURCE_LOCATION (new_decl),
> +             "%qD conflicts for version %qB",
> +             new_decl, &old_target_attr);
> +      inform (DECL_SOURCE_LOCATION (old_decl),
> +           "previous declaration of %qD",
> +           old_decl);
> +      return true;
> +    }
> +
> +  if (old_target_clones_attr)
> +    {
> +      gcc_assert (new_target_attr.is_valid ());
> +
> +      error_at (DECL_SOURCE_LOCATION (new_decl),
> +             "%qD conflicts with a previous declaration for version %qB",
> +             new_decl, &new_target_attr);
> +      inform (DECL_SOURCE_LOCATION (old_decl),
> +           "previous declaration of %qD",
> +           old_decl);
> +      return true;
> +    }
> +
> +  /* The only remaining case is two target_version annoteted decls.  Must
> +     be mergeable as otherwise are distinct.  */
> +  return false;
> +}
> +
>  void
>  tree_cc_finalize (void)
>  {
> diff --git a/gcc/tree.h b/gcc/tree.h
> index b22dce5214a..b1d14341666 100644
> --- a/gcc/tree.h
> +++ b/gcc/tree.h
> @@ -7116,4 +7116,9 @@ extern auto_vec<string_slice> get_clone_versions
>  extern auto_vec<string_slice> get_clone_attr_versions
>    (const tree, int *, bool = true);
>  
> +/* Checks if two decls define any overlapping versions.  */
> +extern bool disjoint_version_decls (tree, tree);
> +/* Checks if two overlapping decls are not mergeable.  */
> +extern bool diagnose_versioned_decls (tree, tree);
> +
>  #endif  /* GCC_TREE_H  */
> -- 
> 2.34.1
> 

-- 
Alfie Richards

Reply via email to