Ping: [PATCH] Allow target attributes in non-gnu namespaces

Message ID mptmsvrawsm.fsf@arm.com
State Unresolved
Headers
Series Ping: [PATCH] Allow target attributes in non-gnu namespaces |

Checks

Context Check Description
snail/gcc-patch-check warning Git am fail log

Commit Message

Richard Sandiford Nov. 6, 2023, 12:30 p.m. UTC
  This is a ping+rebase of the patch below.  I've also optimised the
handling of ignored attributes so that we don't register empty tables.
There was also a typo in the jit changes (which I had tested, but the
typo didn't seem to cause a failure).

Retested on aarch64-linux-gnu & x86_64-linux-gnu.  The original was
also tested on the full target list in config-list.mk.

Iain has already approved the D parts (thanks!).  OK for the rest?

And sorry to be pinging something when I'm behind on reviews myself...

---

Currently there are four static sources of attributes:

- LANG_HOOKS_ATTRIBUTE_TABLE
- LANG_HOOKS_COMMON_ATTRIBUTE_TABLE
- LANG_HOOKS_FORMAT_ATTRIBUTE_TABLE
- TARGET_ATTRIBUTE_TABLE

All of the attributes in these tables go in the "gnu" namespace.
This means that they can use the traditional GNU __attribute__((...))
syntax and the standard [[gnu::...]] syntax.

Standard attributes are registered dynamically with a null namespace.
There are no supported attributes in other namespaces (clang, vendor
namespaces, etc.).

This patch tries to generalise things by making the namespace
part of the attribute specification.

It's usual for multiple attributes to be defined in the same namespace,
so rather than adding the namespace to each individual definition,
it seemed better to group attributes in the same namespace together.
This would also allow us to reuse the same table for clang attributes
that are written with the GNU syntax, or other similar situations
where the attribute can be accessed via multiple "spellings".

The patch therefore adds a scoped_attribute_specs that contains
a namespace and a list of attributes in that namespace.

It's still possible to have multiple scoped_attribute_specs
for the same namespace.  E.g. it makes sense to keep the
C++-specific, C/C++-common, and format-related attributes in
separate tables, even though they're all GNU attributes.

Current lists of attributes are terminated by a null name.
Rather than keep that for the new structure, it seemed neater
to use an array_slice.  This also makes the tables slighly more
compact.

In general, a target might want to support attributes in multiple
namespaces.  Rather than have a separate hook for each possibility
(like the three langhooks above), it seemed better to make
TARGET_ATTRIBUTE_TABLE a table of tables.  Specifically, it's
an array_slice of scoped_attribute_specs.

We can do the same thing for langhooks, which allows the three hooks
above to be merged into a single LANG_HOOKS_ATTRIBUTE_TABLE.
It also allows the standard attributes to be registered statically
and checked by the usual attribs.cc checks.

The patch adds a TARGET_GNU_ATTRIBUTES helper for the common case
in which a target wants a single table of gnu attributes.  It can
only be used if the table is free of preprocessor directives.

There are probably other things we need to do to make vendor namespaces
work smoothly.  E.g. in principle it would be good to make exclusion
sets namespace-aware.  But to some extent we have that with standard
vs. gnu attributes too.  This patch is just supposed to be a first step.

gcc/
	* attribs.h (scoped_attribute_specs): New structure.
	(register_scoped_attributes): Take a reference to a
	scoped_attribute_specs instead of separate namespace and array
	parameters.
	* plugin.h (register_scoped_attributes): Likewise.
	* attribs.cc (register_scoped_attributes): Likewise.
	(attribute_tables): Change into an array of scoped_attribute_specs
	pointers.  Reduce to 1 element for frontends and 1 element for targets.
	(empty_attribute_table): Delete.
	(check_attribute_tables): Update for changes to attribute_tables.
	Use a hash_set to identify duplicates.
	(handle_ignored_attributes_option): Update for above changes.
	(init_attributes): Likewise.
	(excl_pair): Delete.
	(test_attribute_exclusions): Update for above changes.  Don't
	enforce symmetry for standard attributes in the top-level namespace.
	* langhooks-def.h (LANG_HOOKS_COMMON_ATTRIBUTE_TABLE): Delete.
	(LANG_HOOKS_FORMAT_ATTRIBUTE_TABLE): Likewise.
	(LANG_HOOKS_INITIALIZER): Update accordingly.
	(LANG_HOOKS_ATTRIBUTE_TABLE): Define to an empty constructor.
	* langhooks.h (lang_hooks::common_attribute_table): Delete.
	(lang_hooks::format_attribute_table): Likewise.
	(lang_hooks::attribute_table): Redefine to an array of
	scoped_attribute_specs pointers.
	* target-def.h (TARGET_GNU_ATTRIBUTES): New macro.
	* target.def (attribute_spec): Redefine to return an array of
	scoped_attribute_specs pointers.
	* tree-inline.cc (function_attribute_inlinable_p): Update accordingly.
	* doc/tm.texi: Regenerate.
	* config/aarch64/aarch64.cc (aarch64_attribute_table): Define using
	TARGET_GNU_ATTRIBUTES.
	* config/alpha/alpha.cc (vms_attribute_table): Likewise.
	* config/avr/avr.cc (avr_attribute_table): Likewise.
	* config/bfin/bfin.cc (bfin_attribute_table): Likewise.
	* config/bpf/bpf.cc (bpf_attribute_table): Likewise.
	* config/csky/csky.cc (csky_attribute_table): Likewise.
	* config/epiphany/epiphany.cc (epiphany_attribute_table): Likewise.
	* config/gcn/gcn.cc (gcn_attribute_table): Likewise.
	* config/h8300/h8300.cc (h8300_attribute_table): Likewise.
	* config/loongarch/loongarch.cc (loongarch_attribute_table): Likewise.
	* config/m32c/m32c.cc (m32c_attribute_table): Likewise.
	* config/m32r/m32r.cc (m32r_attribute_table): Likewise.
	* config/m68k/m68k.cc (m68k_attribute_table): Likewise.
	* config/mcore/mcore.cc (mcore_attribute_table): Likewise.
	* config/microblaze/microblaze.cc (microblaze_attribute_table):
	Likewise.
	* config/mips/mips.cc (mips_attribute_table): Likewise.
	* config/msp430/msp430.cc (msp430_attribute_table): Likewise.
	* config/nds32/nds32.cc (nds32_attribute_table): Likewise.
	* config/nvptx/nvptx.cc (nvptx_attribute_table): Likewise.
	* config/riscv/riscv.cc (riscv_attribute_table): Likewise.
	* config/rl78/rl78.cc (rl78_attribute_table): Likewise.
	* config/rx/rx.cc (rx_attribute_table): Likewise.
	* config/s390/s390.cc (s390_attribute_table): Likewise.
	* config/sh/sh.cc (sh_attribute_table): Likewise.
	* config/sparc/sparc.cc (sparc_attribute_table): Likewise.
	* config/stormy16/stormy16.cc (xstormy16_attribute_table): Likewise.
	* config/v850/v850.cc (v850_attribute_table): Likewise.
	* config/visium/visium.cc (visium_attribute_table): Likewise.
	* config/arc/arc.cc (arc_attribute_table): Likewise.  Move further
	down file.
	* config/arm/arm.cc (arm_attribute_table): Update for above changes,
	using...
	(arm_gnu_attributes, arm_gnu_attribute_table): ...these new globals.
	* config/i386/i386-options.h (ix86_attribute_table): Delete.
	(ix86_gnu_attribute_table): Declare.
	* config/i386/i386-options.cc (ix86_attribute_table): Replace with...
	(ix86_gnu_attributes, ix86_gnu_attribute_table): ...these two globals.
	* config/i386/i386.cc (ix86_attribute_table): Define as an array of
	scoped_attribute_specs pointers.
	* config/ia64/ia64.cc (ia64_attribute_table): Update for above changes,
	using...
	(ia64_gnu_attributes, ia64_gnu_attribute_table): ...these new globals.
	* config/rs6000/rs6000.cc (rs6000_attribute_table): Update for above
	changes, using...
	(rs6000_gnu_attributes, rs6000_gnu_attribute_table): ...these new
	globals.

gcc/ada/
	* gcc-interface/gigi.h (gnat_internal_attribute_table): Change
	type to scoped_attribute_specs.
	* gcc-interface/utils.cc (gnat_internal_attribute_table): Likewise,
	using...
	(gnat_internal_attributes): ...this as the underlying array.
	* gcc-interface/misc.cc (gnat_attribute_table): New global.
	(LANG_HOOKS_ATTRIBUTE_TABLE): Use it.

gcc/c-family/
	* c-common.h (c_common_attribute_table): Replace with...
	(c_common_gnu_attribute_table): ...this.
	(c_common_format_attribute_table): Change type to
	scoped_attribute_specs.
	* c-attribs.cc (c_common_attribute_table): Replace with...
	(c_common_gnu_attributes, c_common_gnu_attribute_table): ...these
	new globals.
	(c_common_format_attribute_table): Change type to
	scoped_attribute_specs, using...
	(c_common_format_attributes): ...this as the underlying array.

gcc/c/
	* c-tree.h (std_attribute_table): Declare.
	* c-decl.cc (std_attribute_table): Change type to
	scoped_attribute_specs, using...
	(std_attributes): ...this as the underlying array.
	(c_init_decl_processing): Remove call to register_scoped_attributes.
	* c-objc-common.h (c_objc_attribute_table): New global.
	(LANG_HOOKS_ATTRIBUTE_TABLE): Use it.
	(LANG_HOOKS_COMMON_ATTRIBUTE_TABLE): Delete.
	(LANG_HOOKS_FORMAT_ATTRIBUTE_TABLE): Delete.

gcc/cp/
	* cp-tree.h (cxx_attribute_table): Delete.
	(cxx_gnu_attribute_table, std_attribute_table): Declare.
	* cp-objcp-common.h (LANG_HOOKS_COMMON_ATTRIBUTE_TABLE): Delete.
	(LANG_HOOKS_FORMAT_ATTRIBUTE_TABLE): Delete.
	(cp_objcp_attribute_table): New table.
	(LANG_HOOKS_ATTRIBUTE_TABLE): Redefine.
	* tree.cc (cxx_attribute_table): Replace with...
	(cxx_gnu_attributes, cxx_gnu_attribute_table): ...these globals.
	(std_attribute_table): Change type to scoped_attribute_specs, using...
	(std_attributes): ...this as the underlying array.
	(init_tree): Remove call to register_scoped_attributes.

gcc/d/
	* d-tree.h (d_langhook_attribute_table): Replace with...
	(d_langhook_gnu_attribute_table): ...this.
	(d_langhook_common_attribute_table): Change type to
	scoped_attribute_specs.
	* d-attribs.cc (d_langhook_common_attribute_table): Change type to
	scoped_attribute_specs, using...
	(d_langhook_common_attributes): ...this as the underlying array.
	(d_langhook_attribute_table): Replace with...
	(d_langhook_gnu_attributes, d_langhook_gnu_attribute_table): ...these
	new globals.
	(uda_attribute_p): Update accordingly, and update for new
	targetm.attribute_table type.
	* d-lang.cc (d_langhook_attribute_table): New global.
	(LANG_HOOKS_COMMON_ATTRIBUTE_TABLE): Delete.

gcc/fortran/
	* f95-lang.cc: Include attribs.h.
	(gfc_attribute_table): Change to an array of scoped_attribute_specs
	pointers, using...
	(gfc_gnu_attributes, gfc_gnu_attribute_table): ...these new globals.

gcc/jit/
	* dummy-frontend.cc (jit_format_attribute_table): Change type to
	scoped_attribute_specs, using...
	(jit_format_attributes): ...this as the underlying array.
	(jit_attribute_table): Change to an array of scoped_attribute_specs
	pointers, using...
	(jit_gnu_attributes, jit_gnu_attribute_table): ...these new globals
	for the original array.  Include the format attributes.
	(LANG_HOOKS_COMMON_ATTRIBUTE_TABLE): Delete.
	(LANG_HOOKS_FORMAT_ATTRIBUTE_TABLE): Delete.
	(LANG_HOOKS_ATTRIBUTE_TABLE): Define.

gcc/lto/
	* lto-lang.cc (lto_format_attribute_table): Change type to
	scoped_attribute_specs, using...
	(lto_format_attributes): ...this as the underlying array.
	(lto_attribute_table): Change to an array of scoped_attribute_specs
	pointers, using...
	(lto_gnu_attributes, lto_gnu_attribute_table): ...these new globals
	for the original array.  Include the format attributes.
	(LANG_HOOKS_COMMON_ATTRIBUTE_TABLE): Delete.
	(LANG_HOOKS_FORMAT_ATTRIBUTE_TABLE): Delete.
	(LANG_HOOKS_ATTRIBUTE_TABLE): Define.
---
 gcc/ada/gcc-interface/gigi.h        |   2 +-
 gcc/ada/gcc-interface/misc.cc       |   7 +-
 gcc/ada/gcc-interface/utils.cc      |   8 +-
 gcc/attribs.cc                      | 221 ++++++++++++----------------
 gcc/attribs.h                       |  12 +-
 gcc/c-family/c-attribs.cc           |  20 ++-
 gcc/c-family/c-common.h             |   4 +-
 gcc/c/c-decl.cc                     |  12 +-
 gcc/c/c-objc-common.h               |  14 +-
 gcc/c/c-tree.h                      |   2 +
 gcc/config/aarch64/aarch64.cc       |   7 +-
 gcc/config/alpha/alpha.cc           |   7 +-
 gcc/config/arc/arc.cc               |  74 +++++-----
 gcc/config/arm/arm.cc               |  15 +-
 gcc/config/avr/avr.cc               |   7 +-
 gcc/config/bfin/bfin.cc             |   7 +-
 gcc/config/bpf/bpf.cc               |   9 +-
 gcc/config/csky/csky.cc             |   7 +-
 gcc/config/epiphany/epiphany.cc     |   7 +-
 gcc/config/gcn/gcn.cc               |   8 +-
 gcc/config/h8300/h8300.cc           |   7 +-
 gcc/config/i386/i386-options.cc     |  10 +-
 gcc/config/i386/i386-options.h      |   2 +-
 gcc/config/i386/i386.cc             |   5 +
 gcc/config/ia64/ia64.cc             |  15 +-
 gcc/config/loongarch/loongarch.cc   |   8 +-
 gcc/config/m32c/m32c.cc             |   7 +-
 gcc/config/m32r/m32r.cc             |   7 +-
 gcc/config/m68k/m68k.cc             |   7 +-
 gcc/config/mcore/mcore.cc           |   7 +-
 gcc/config/microblaze/microblaze.cc |   7 +-
 gcc/config/mips/mips.cc             |   7 +-
 gcc/config/msp430/msp430.cc         |   8 +-
 gcc/config/nds32/nds32.cc           |   9 +-
 gcc/config/nvptx/nvptx.cc           |   7 +-
 gcc/config/riscv/riscv.cc           |   9 +-
 gcc/config/rl78/rl78.cc             |   7 +-
 gcc/config/rs6000/rs6000.cc         |  13 +-
 gcc/config/rx/rx.cc                 |   7 +-
 gcc/config/s390/s390.cc             |   9 +-
 gcc/config/sh/sh.cc                 |   7 +-
 gcc/config/sparc/sparc.cc           |   7 +-
 gcc/config/stormy16/stormy16.cc     |   7 +-
 gcc/config/v850/v850.cc             |   7 +-
 gcc/config/visium/visium.cc         |   7 +-
 gcc/cp/cp-objcp-common.h            |  15 +-
 gcc/cp/cp-tree.h                    |   3 +-
 gcc/cp/tree.cc                      |  16 +-
 gcc/d/d-attribs.cc                  |  35 ++---
 gcc/d/d-lang.cc                     |   8 +-
 gcc/d/d-tree.h                      |   4 +-
 gcc/doc/tm.texi                     |  33 ++++-
 gcc/fortran/f95-lang.cc             |  14 +-
 gcc/jit/dummy-frontend.cc           |  32 ++--
 gcc/langhooks-def.h                 |   6 +-
 gcc/langhooks.h                     |   4 +-
 gcc/lto/lto-lang.cc                 |  30 ++--
 gcc/plugin.h                        |   3 +-
 gcc/target-def.h                    |  14 ++
 gcc/target.def                      |  35 ++++-
 gcc/tree-inline.cc                  |   7 +-
 61 files changed, 494 insertions(+), 408 deletions(-)
  

Patch

diff --git a/gcc/ada/gcc-interface/gigi.h b/gcc/ada/gcc-interface/gigi.h
index eb5496f50db..63ccf311c23 100644
--- a/gcc/ada/gcc-interface/gigi.h
+++ b/gcc/ada/gcc-interface/gigi.h
@@ -350,7 +350,7 @@  struct attrib
 };
 
 /* Table of machine-independent internal attributes.  */
-extern const struct attribute_spec gnat_internal_attribute_table[];
+extern const struct scoped_attribute_specs gnat_internal_attribute_table;
 
 /* Define the entries in the standard data array.  */
 enum standard_datatypes
diff --git a/gcc/ada/gcc-interface/misc.cc b/gcc/ada/gcc-interface/misc.cc
index 7d6d4466d56..01e8267f884 100644
--- a/gcc/ada/gcc-interface/misc.cc
+++ b/gcc/ada/gcc-interface/misc.cc
@@ -1352,6 +1352,11 @@  get_lang_specific (tree node)
   return TYPE_LANG_SPECIFIC (node);
 }
 
+const struct scoped_attribute_specs *const gnat_attribute_table[] =
+{
+  &gnat_internal_attribute_table
+};
+
 /* Definitions for our language-specific hooks.  */
 
 #undef  LANG_HOOKS_NAME
@@ -1417,7 +1422,7 @@  get_lang_specific (tree node)
 #undef  LANG_HOOKS_GET_FIXED_POINT_TYPE_INFO
 #define LANG_HOOKS_GET_FIXED_POINT_TYPE_INFO gnat_get_fixed_point_type_info
 #undef  LANG_HOOKS_ATTRIBUTE_TABLE
-#define LANG_HOOKS_ATTRIBUTE_TABLE	gnat_internal_attribute_table
+#define LANG_HOOKS_ATTRIBUTE_TABLE	gnat_attribute_table
 #undef  LANG_HOOKS_BUILTIN_FUNCTION
 #define LANG_HOOKS_BUILTIN_FUNCTION	gnat_builtin_function
 #undef  LANG_HOOKS_INIT_TS
diff --git a/gcc/ada/gcc-interface/utils.cc b/gcc/ada/gcc-interface/utils.cc
index 4e2ed173fbe..c38e0e6f57d 100644
--- a/gcc/ada/gcc-interface/utils.cc
+++ b/gcc/ada/gcc-interface/utils.cc
@@ -136,7 +136,7 @@  static tree fake_attribute_handler (tree *, tree, tree, int, bool *);
 
 /* Table of machine-independent internal attributes for Ada.  We support
    this minimal set of attributes to accommodate the needs of builtins.  */
-const struct attribute_spec gnat_internal_attribute_table[] =
+static const attribute_spec gnat_internal_attributes[] =
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -217,9 +217,11 @@  const struct attribute_spec gnat_internal_attribute_table[] =
   /* This is handled entirely in the front end.  */
   { "hardbool",     0, 0,  false, true, false, true,
     fake_attribute_handler, NULL },
+};
 
-  { NULL,           0, 0,  false, false, false, false,
-    NULL, NULL }
+const scoped_attribute_specs gnat_internal_attribute_table =
+{
+  "gnu", gnat_internal_attributes
 };
 
 /* Associates a GNAT tree node to a GCC tree node. It is used in
diff --git a/gcc/attribs.cc b/gcc/attribs.cc
index d1d9e5a28c1..6725fe78f2c 100644
--- a/gcc/attribs.cc
+++ b/gcc/attribs.cc
@@ -39,7 +39,7 @@  along with GCC; see the file COPYING3.  If not see
 
 /* Table of the tables of attributes (common, language, format, machine)
    searched.  */
-static const struct attribute_spec *attribute_tables[4];
+static array_slice<const scoped_attribute_specs *const> attribute_tables[2];
 
 /* Substring representation.  */
 
@@ -102,13 +102,6 @@  static const struct attribute_spec *lookup_scoped_attribute_spec (const_tree,
 
 static bool attributes_initialized = false;
 
-/* Default empty table of attributes.  */
-
-static const struct attribute_spec empty_attribute_table[] =
-{
-  { NULL, 0, 0, false, false, false, false, NULL, NULL }
-};
-
 /* Return base name of the attribute.  Ie '__attr__' is turned into 'attr'.
    To avoid need for copying, we simply return length of the string.  */
 
@@ -118,21 +111,19 @@  extract_attribute_substring (struct substring *str)
   canonicalize_attr_name (str->str, str->length);
 }
 
-/* Insert an array of attributes ATTRIBUTES into a namespace.  This
-   array must be NULL terminated.  NS is the name of attribute
-   namespace.  IGNORED_P is true iff all unknown attributes in this
-   namespace should be ignored for the purposes of -Wattributes.  The
-   function returns the namespace into which the attributes have been
-   registered.  */
+/* Insert SPECS into its namespace.  IGNORED_P is true iff all unknown
+   attributes in this namespace should be ignored for the purposes of
+   -Wattributes.  The function returns the namespace into which the
+   attributes have been registered.  */
 
 scoped_attributes *
-register_scoped_attributes (const struct attribute_spec *attributes,
-			    const char *ns, bool ignored_p /*=false*/)
+register_scoped_attributes (const scoped_attribute_specs &specs,
+			    bool ignored_p /*=false*/)
 {
   scoped_attributes *result = NULL;
 
   /* See if we already have attributes in the namespace NS.  */
-  result = find_attribute_namespace (ns);
+  result = find_attribute_namespace (specs.ns);
 
   if (result == NULL)
     {
@@ -143,7 +134,7 @@  register_scoped_attributes (const struct attribute_spec *attributes,
 	attributes_table.create (64);
 
       memset (&sa, 0, sizeof (sa));
-      sa.ns = ns;
+      sa.ns = specs.ns;
       sa.attributes.create (64);
       sa.ignored_p = ignored_p;
       result = attributes_table.safe_push (sa);
@@ -153,10 +144,10 @@  register_scoped_attributes (const struct attribute_spec *attributes,
     result->ignored_p |= ignored_p;
 
   /* Really add the attributes to their namespace now.  */
-  for (unsigned i = 0; attributes[i].name != NULL; ++i)
+  for (const attribute_spec &attribute : specs.attributes)
     {
-      result->attributes.safe_push (attributes[i]);
-      register_scoped_attribute (&attributes[i], result);
+      result->attributes.safe_push (attribute);
+      register_scoped_attribute (&attribute, result);
     }
 
   gcc_assert (result != NULL);
@@ -183,49 +174,40 @@  find_attribute_namespace (const char* ns)
 static void
 check_attribute_tables (void)
 {
-  for (size_t i = 0; i < ARRAY_SIZE (attribute_tables); i++)
-    for (size_t j = 0; attribute_tables[i][j].name != NULL; j++)
-      {
-	/* The name must not begin and end with __.  */
-	const char *name = attribute_tables[i][j].name;
-	int len = strlen (name);
+  hash_set<pair_hash<nofree_string_hash, nofree_string_hash>> names;
 
-	gcc_assert (!(name[0] == '_' && name[1] == '_'
-		      && name[len - 1] == '_' && name[len - 2] == '_'));
+  for (auto scoped_array : attribute_tables)
+    for (auto scoped_attributes : scoped_array)
+      for (const attribute_spec &attribute : scoped_attributes->attributes)
+	{
+	  /* The name must not begin and end with __.  */
+	  const char *name = attribute.name;
+	  int len = strlen (name);
+
+	  gcc_assert (!(name[0] == '_' && name[1] == '_'
+			&& name[len - 1] == '_' && name[len - 2] == '_'));
 
-	/* The minimum and maximum lengths must be consistent.  */
-	gcc_assert (attribute_tables[i][j].min_length >= 0);
+	  /* The minimum and maximum lengths must be consistent.  */
+	  gcc_assert (attribute.min_length >= 0);
 
-	gcc_assert (attribute_tables[i][j].max_length == -1
-		    || (attribute_tables[i][j].max_length
-			>= attribute_tables[i][j].min_length));
+	  gcc_assert (attribute.max_length == -1
+		      || attribute.max_length >= attribute.min_length);
 
-	/* An attribute cannot require both a DECL and a TYPE.  */
-	gcc_assert (!attribute_tables[i][j].decl_required
-		    || !attribute_tables[i][j].type_required);
+	  /* An attribute cannot require both a DECL and a TYPE.  */
+	  gcc_assert (!attribute.decl_required
+		      || !attribute.type_required);
 
 	  /* If an attribute requires a function type, in particular
 	     it requires a type.  */
-	gcc_assert (!attribute_tables[i][j].function_type_required
-		    || attribute_tables[i][j].type_required);
-      }
-
-  /* Check that each name occurs just once in each table.  */
-  for (size_t i = 0; i < ARRAY_SIZE (attribute_tables); i++)
-    for (size_t j = 0; attribute_tables[i][j].name != NULL; j++)
-      for (size_t k = j + 1; attribute_tables[i][k].name != NULL; k++)
-	gcc_assert (strcmp (attribute_tables[i][j].name,
-			    attribute_tables[i][k].name));
-
-  /* Check that no name occurs in more than one table.  Names that
-     begin with '*' are exempt, and may be overridden.  */
-  for (size_t i = 0; i < ARRAY_SIZE (attribute_tables); i++)
-    for (size_t j = i + 1; j < ARRAY_SIZE (attribute_tables); j++)
-      for (size_t k = 0; attribute_tables[i][k].name != NULL; k++)
-	for (size_t l = 0; attribute_tables[j][l].name != NULL; l++)
-	  gcc_assert (attribute_tables[i][k].name[0] == '*'
-		      || strcmp (attribute_tables[i][k].name,
-				 attribute_tables[j][l].name));
+	  gcc_assert (!attribute.function_type_required
+		      || attribute.type_required);
+
+	  /* Check that no name occurs more than once.  Names that
+	     begin with '*' are exempt, and may be overridden.  */
+	  const char *ns = scoped_attributes->ns;
+	  if (name[0] != '*' && names.add ({ ns ? ns : "", name }))
+	    gcc_unreachable ();
+	}
 }
 
 /* Used to stash pointers to allocated memory so that we can free them at
@@ -281,7 +263,7 @@  handle_ignored_attributes_option (vec<char *> *v)
       canonicalize_attr_name (vendor_start, vendor_len);
       /* We perform all this hijinks so that we don't have to copy OPT.  */
       tree vendor_id = get_identifier_with_length (vendor_start, vendor_len);
-      const char *attr;
+      array_slice<const attribute_spec> attrs;
       /* In the "vendor::" case, we should ignore *any* attribute coming
 	 from this attribute namespace.  */
       if (attr_len > 0)
@@ -293,22 +275,23 @@  handle_ignored_attributes_option (vec<char *> *v)
 	    }
 	  canonicalize_attr_name (attr_start, attr_len);
 	  tree attr_id = get_identifier_with_length (attr_start, attr_len);
-	  attr = IDENTIFIER_POINTER (attr_id);
+	  const char *attr = IDENTIFIER_POINTER (attr_id);
 	  /* If we've already seen this vendor::attr, ignore it.  Attempting to
 	     register it twice would lead to a crash.  */
 	  if (lookup_scoped_attribute_spec (vendor_id, attr_id))
 	    continue;
+	  /* Create a table with extra attributes which we will register.
+	     We can't free it here, so squirrel away the pointers.  */
+	  attribute_spec *table = new attribute_spec {
+	    attr, 0, -2, false, false, false, false, nullptr, nullptr
+	  };
+	  ignored_attributes_table.safe_push (table);
+	  attrs = { table, 1 };
 	}
-      else
-	attr = nullptr;
-      /* Create a table with extra attributes which we will register.
-	 We can't free it here, so squirrel away the pointers.  */
-      attribute_spec *table = new attribute_spec[2];
-      ignored_attributes_table.safe_push (table);
-      table[0] = { attr, 0, -2, false, false, false, false, nullptr, nullptr };
-      table[1] = { nullptr, 0, 0, false, false, false, false, nullptr,
-		   nullptr };
-      register_scoped_attributes (table, IDENTIFIER_POINTER (vendor_id), !attr);
+      const scoped_attribute_specs scoped_specs = {
+	IDENTIFIER_POINTER (vendor_id), attrs
+      };
+      register_scoped_attributes (scoped_specs, attrs.empty ());
     }
 }
 
@@ -328,27 +311,18 @@  free_attr_data ()
 void
 init_attributes (void)
 {
-  size_t i;
-
   if (attributes_initialized)
     return;
 
-  attribute_tables[0] = lang_hooks.common_attribute_table;
-  attribute_tables[1] = lang_hooks.attribute_table;
-  attribute_tables[2] = lang_hooks.format_attribute_table;
-  attribute_tables[3] = targetm.attribute_table;
-
-  /* Translate NULL pointers to pointers to the empty table.  */
-  for (i = 0; i < ARRAY_SIZE (attribute_tables); i++)
-    if (attribute_tables[i] == NULL)
-      attribute_tables[i] = empty_attribute_table;
+  attribute_tables[0] = lang_hooks.attribute_table;
+  attribute_tables[1] = targetm.attribute_table;
 
   if (flag_checking)
     check_attribute_tables ();
 
-  for (i = 0; i < ARRAY_SIZE (attribute_tables); ++i)
-    /* Put all the GNU attributes into the "gnu" namespace.  */
-    register_scoped_attributes (attribute_tables[i], "gnu");
+  for (auto scoped_array : attribute_tables)
+    for (auto scoped_attributes : scoped_array)
+      register_scoped_attributes (*scoped_attributes);
 
   vec<char *> *ignored = (vec<char *> *) flag_ignored_attributes;
   handle_ignored_attributes_option (ignored);
@@ -2642,10 +2616,6 @@  attr_access::array_as_string (tree type) const
 namespace selftest
 {
 
-/* Helper types to verify the consistency attribute exclusions.  */
-
-typedef std::pair<const char *, const char *> excl_pair;
-
 /* Self-test to verify that each attribute exclusion is symmetric,
    meaning that if attribute A is encoded as incompatible with
    attribute B then the opposite relationship is also encoded.
@@ -2660,55 +2630,54 @@  test_attribute_exclusions ()
   /* Iterate over the array of attribute tables first (with TI0 as
      the index) and over the array of attribute_spec in each table
      (with SI0 as the index).  */
-  const size_t ntables = ARRAY_SIZE (attribute_tables);
+  hash_set<excl_hash_traits> excl_set;
 
-  /* Set of pairs of mutually exclusive attributes.  */
-  typedef hash_set<excl_hash_traits> exclusion_set;
-  exclusion_set excl_set;
+  for (auto scoped_array : attribute_tables)
+    for (auto scoped_attributes : scoped_array)
+      for (const attribute_spec &attribute : scoped_attributes->attributes)
+	{
+	  const attribute_spec::exclusions *excl = attribute.exclude;
 
-  for (size_t ti0 = 0; ti0 != ntables; ++ti0)
-    for (size_t s0 = 0; attribute_tables[ti0][s0].name; ++s0)
-      {
-	const attribute_spec::exclusions *excl
-	  = attribute_tables[ti0][s0].exclude;
+	  /* Skip each attribute that doesn't define exclusions.  */
+	  if (!excl)
+	    continue;
 
-	/* Skip each attribute that doesn't define exclusions.  */
-	if (!excl)
-	  continue;
+	  /* Skip standard (non-GNU) attributes, since currently the
+	     exclusions are implicitly for GNU attributes only.
+	     Also, C++ likely and unlikely get rewritten to gnu::hot
+	     and gnu::cold, so symmetry isn't necessary there.  */
+	  if (!scoped_attributes->ns)
+	    continue;
 
-	const char *attr_name = attribute_tables[ti0][s0].name;
+	  const char *attr_name = attribute.name;
 
-	/* Iterate over the set of exclusions for every attribute
-	   (with EI0 as the index) adding the exclusions defined
-	   for each to the set.  */
-	for (size_t ei0 = 0; excl[ei0].name; ++ei0)
-	  {
-	    const char *excl_name = excl[ei0].name;
+	  /* Iterate over the set of exclusions for every attribute
+	     (with EI0 as the index) adding the exclusions defined
+	     for each to the set.  */
+	  for (size_t ei0 = 0; excl[ei0].name; ++ei0)
+	    {
+	      const char *excl_name = excl[ei0].name;
 
-	    if (!strcmp (attr_name, excl_name))
-	      continue;
+	      if (!strcmp (attr_name, excl_name))
+		continue;
 
-	    excl_set.add (excl_pair (attr_name, excl_name));
-	  }
-      }
+	      excl_set.add ({ attr_name, excl_name });
+	    }
+	}
 
   /* Traverse the set of mutually exclusive pairs of attributes
      and verify that they are symmetric.  */
-  for (exclusion_set::iterator it = excl_set.begin ();
-       it != excl_set.end ();
-       ++it)
-    {
-      if (!excl_set.contains (excl_pair ((*it).second, (*it).first)))
-	{
-	  /* An exclusion for an attribute has been found that
-	     doesn't have a corresponding exclusion in the opposite
-	     direction.  */
-	  char desc[120];
-	  sprintf (desc, "'%s' attribute exclusion '%s' must be symmetric",
-		   (*it).first, (*it).second);
-	  fail (SELFTEST_LOCATION, desc);
-	}
-    }
+  for (auto excl_pair : excl_set)
+    if (!excl_set.contains ({ excl_pair.second, excl_pair.first }))
+      {
+	/* An exclusion for an attribute has been found that
+	   doesn't have a corresponding exclusion in the opposite
+	   direction.  */
+	char desc[120];
+	sprintf (desc, "'%s' attribute exclusion '%s' must be symmetric",
+		 excl_pair.first, excl_pair.second);
+	fail (SELFTEST_LOCATION, desc);
+      }
 }
 
 void
diff --git a/gcc/attribs.h b/gcc/attribs.h
index 84a43658a70..fdeebff1cd9 100644
--- a/gcc/attribs.h
+++ b/gcc/attribs.h
@@ -20,6 +20,13 @@  along with GCC; see the file COPYING3.  If not see
 #ifndef GCC_ATTRIBS_H
 #define GCC_ATTRIBS_H
 
+/* A set of attributes that belong to the same namespace, given by NS.  */
+struct scoped_attribute_specs
+{
+  const char *ns;
+  array_slice<const attribute_spec> attributes;
+};
+
 extern const struct attribute_spec *lookup_attribute_spec (const_tree);
 extern void free_attr_data ();
 extern void init_attributes (void);
@@ -42,9 +49,8 @@  extern tree make_attribute (const char *, const char *, tree);
 extern bool attribute_ignored_p (tree);
 extern bool attribute_ignored_p (const attribute_spec *const);
 
-extern struct scoped_attributes* register_scoped_attributes (const struct attribute_spec *,
-							     const char *,
-							     bool = false);
+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);
diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc
index a041c3b91eb..cf54ef68f20 100644
--- a/gcc/c-family/c-attribs.cc
+++ b/gcc/c-family/c-attribs.cc
@@ -288,7 +288,7 @@  static const struct attribute_spec::exclusions attr_stack_protect_exclusions[] =
 /* Table of machine-independent attributes common to all C-like languages.
 
    Current list of processed common attributes: nonnull.  */
-const struct attribute_spec c_common_attribute_table[] =
+const struct attribute_spec c_common_gnu_attributes[] =
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -574,23 +574,31 @@  const struct attribute_spec c_common_attribute_table[] =
   { "fd_arg_write",       1, 1, false, true, true, false,
             handle_fd_arg_attribute, NULL},         
   { "null_terminated_string_arg", 1, 1, false, true, true, false,
-			      handle_null_terminated_string_arg_attribute, NULL},
-  { NULL,                     0, 0, false, false, false, false, NULL, NULL }
+			      handle_null_terminated_string_arg_attribute, NULL}
+};
+
+const struct scoped_attribute_specs c_common_gnu_attribute_table =
+{
+  "gnu", c_common_gnu_attributes
 };
 
 /* Give the specifications for the format attributes, used by C and all
    descendants.
 
    Current list of processed format attributes: format, format_arg.  */
-const struct attribute_spec c_common_format_attribute_table[] =
+const struct attribute_spec c_common_format_attributes[] =
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
   { "format",                 3, 3, false, true,  true, false,
 			      handle_format_attribute, NULL },
   { "format_arg",             1, 1, false, true,  true, false,
-			      handle_format_arg_attribute, NULL },
-  { NULL,                     0, 0, false, false, false, false, NULL, NULL }
+			      handle_format_arg_attribute, NULL }
+};
+
+const struct scoped_attribute_specs c_common_format_attribute_table =
+{
+  "gnu", c_common_format_attributes
 };
 
 /* Returns TRUE iff the attribute indicated by ATTR_ID takes a plain
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 1fdba7ef3ea..92ddad27806 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -821,8 +821,8 @@  enum conversion_safety {
 extern struct visibility_flags visibility_options;
 
 /* Attribute table common to the C front ends.  */
-extern const struct attribute_spec c_common_attribute_table[];
-extern const struct attribute_spec c_common_format_attribute_table[];
+extern const struct scoped_attribute_specs c_common_gnu_attribute_table;
+extern const struct scoped_attribute_specs c_common_format_attribute_table;
 
 /* Pointer to function to lazily generate the VAR_DECL for __FUNCTION__ etc.
    ID is the identifier to use, NAME is the string.
diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index 0076725a61d..547bebe03c9 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -4635,7 +4635,7 @@  handle_std_noreturn_attribute (tree *node, tree name, tree args,
 }
 
 /* Table of supported standard (C2x) attributes.  */
-const struct attribute_spec std_attribute_table[] =
+static const attribute_spec std_attributes[] =
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -4650,8 +4650,12 @@  const struct attribute_spec std_attribute_table[] =
   { "nodiscard", 0, 1, false, false, false, false,
     handle_nodiscard_attribute, NULL },
   { "noreturn", 0, 0, false, false, false, false,
-    handle_std_noreturn_attribute, NULL },
-  { NULL, 0, 0, false, false, false, false, NULL, NULL }
+    handle_std_noreturn_attribute, NULL }
+};
+
+const scoped_attribute_specs std_attribute_table =
+{
+  nullptr, std_attributes
 };
 
 /* Create the predefined scalar types of C,
@@ -4667,8 +4671,6 @@  c_init_decl_processing (void)
   /* Initialize reserved words for parser.  */
   c_parse_init ();
 
-  register_scoped_attributes (std_attribute_table, NULL);
-
   current_function_decl = NULL_TREE;
 
   gcc_obstack_init (&parser_obstack);
diff --git a/gcc/c/c-objc-common.h b/gcc/c/c-objc-common.h
index ede451cef6b..4f1925c8b07 100644
--- a/gcc/c/c-objc-common.h
+++ b/gcc/c/c-objc-common.h
@@ -72,11 +72,15 @@  along with GCC; see the file COPYING3.  If not see
 #undef LANG_HOOKS_FINALIZE_EARLY_DEBUG
 #define LANG_HOOKS_FINALIZE_EARLY_DEBUG c_common_finalize_early_debug
 
-/* Attribute hooks.  */
-#undef LANG_HOOKS_COMMON_ATTRIBUTE_TABLE
-#define LANG_HOOKS_COMMON_ATTRIBUTE_TABLE c_common_attribute_table
-#undef LANG_HOOKS_FORMAT_ATTRIBUTE_TABLE
-#define LANG_HOOKS_FORMAT_ATTRIBUTE_TABLE c_common_format_attribute_table
+static const scoped_attribute_specs *const c_objc_attribute_table[] =
+{
+  &std_attribute_table,
+  &c_common_gnu_attribute_table,
+  &c_common_format_attribute_table
+};
+
+#undef LANG_HOOKS_ATTRIBUTE_TABLE
+#define LANG_HOOKS_ATTRIBUTE_TABLE c_objc_attribute_table
 
 #undef LANG_HOOKS_TREE_DUMP_DUMP_TREE_FN
 #define LANG_HOOKS_TREE_DUMP_DUMP_TREE_FN c_dump_tree
diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h
index df6f1cefd02..69e154abdbd 100644
--- a/gcc/c/c-tree.h
+++ b/gcc/c/c-tree.h
@@ -910,6 +910,8 @@  extern vec<tree> incomplete_record_decls;
 
 extern const char *c_get_sarif_source_language (const char *filename);
 
+extern const struct scoped_attribute_specs std_attribute_table;
+
 #if CHECKING_P
 namespace selftest {
   extern void run_c_tests (void);
diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
index cb65ccc8465..920e17d3a95 100644
--- a/gcc/config/aarch64/aarch64.cc
+++ b/gcc/config/aarch64/aarch64.cc
@@ -2839,7 +2839,7 @@  handle_aarch64_vector_pcs_attribute (tree *node, tree name, tree,
 }
 
 /* Table of machine attributes.  */
-static const struct attribute_spec aarch64_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (aarch64_attribute_table,
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -2850,9 +2850,8 @@  static const struct attribute_spec aarch64_attribute_table[] =
 			  NULL },
   { "Advanced SIMD type", 1, 1, false, true,  false, true,  NULL, NULL },
   { "SVE type",		  3, 3, false, true,  false, true,  NULL, NULL },
-  { "SVE sizeless type",  0, 0, false, true,  false, true,  NULL, NULL },
-  { NULL,                 0, 0, false, false, false, false, NULL, NULL }
-};
+  { "SVE sizeless type",  0, 0, false, true,  false, true,  NULL, NULL }
+});
 
 typedef enum aarch64_cond_code
 {
diff --git a/gcc/config/alpha/alpha.cc b/gcc/config/alpha/alpha.cc
index db6b34be9cb..6aa93783226 100644
--- a/gcc/config/alpha/alpha.cc
+++ b/gcc/config/alpha/alpha.cc
@@ -7482,14 +7482,13 @@  common_object_handler (tree *node, tree name ATTRIBUTE_UNUSED,
   return NULL_TREE;
 }
 
-static const struct attribute_spec vms_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (vms_attribute_table,
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
   { COMMON_OBJECT,   0, 1, true,  false, false, false, common_object_handler,
-    NULL },
-  { NULL,            0, 0, false, false, false, false, NULL, NULL }
-};
+    NULL }
+});
 
 void
 vms_output_aligned_decl_common(FILE *file, tree decl, const char *name,
diff --git a/gcc/config/arc/arc.cc b/gcc/config/arc/arc.cc
index e209ad2d327..e87d7614feb 100644
--- a/gcc/config/arc/arc.cc
+++ b/gcc/config/arc/arc.cc
@@ -187,44 +187,6 @@  static tree arc_handle_secure_attribute (tree *, tree, tree, int, bool *);
 static tree arc_handle_uncached_attribute (tree *, tree, tree, int, bool *);
 static tree arc_handle_aux_attribute (tree *, tree, tree, int, bool *);
 
-/* Initialized arc_attribute_table to NULL since arc doesnot have any
-   machine specific supported attributes.  */
-const struct attribute_spec arc_attribute_table[] =
-{
- /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
-      affects_type_identity, handler, exclude } */
-  { "interrupt", 1, 1, true, false, false, true,
-    arc_handle_interrupt_attribute, NULL },
-  /* Function calls made to this symbol must be done indirectly, because
-     it may lie outside of the 21/25 bit addressing range of a normal function
-     call.  */
-  { "long_call",    0, 0, false, true,  true,  false, NULL, NULL },
-  /* Whereas these functions are always known to reside within the 25 bit
-     addressing range of unconditionalized bl.  */
-  { "medium_call",   0, 0, false, true,  true, false, NULL, NULL },
-  /* And these functions are always known to reside within the 21 bit
-     addressing range of blcc.  */
-  { "short_call",   0, 0, false, true,  true,  false, NULL, NULL },
-  /* Function which are not having the prologue and epilogue generated
-     by the compiler.  */
-  { "naked", 0, 0, true, false, false,  false, arc_handle_fndecl_attribute,
-    NULL },
-  /* Functions calls made using jli instruction.  The pointer in JLI
-     table is found latter.  */
-  { "jli_always",    0, 0, false, true,  true, false,  NULL, NULL },
-  /* Functions calls made using jli instruction.  The pointer in JLI
-     table is given as input parameter.  */
-  { "jli_fixed",    1, 1, false, true,  true, false, arc_handle_jli_attribute,
-    NULL },
-  /* Call a function using secure-mode.  */
-  { "secure_call",  1, 1, false, true, true, false, arc_handle_secure_attribute,
-    NULL },
-   /* Bypass caches using .di flag.  */
-  { "uncached", 0, 0, false, true, false, false, arc_handle_uncached_attribute,
-    NULL },
-  { "aux", 0, 1, true, false, false, false, arc_handle_aux_attribute, NULL },
-  { NULL, 0, 0, false, false, false, false, NULL, NULL }
-};
 static int arc_comp_type_attributes (const_tree, const_tree);
 static void arc_file_start (void);
 static void arc_internal_label (FILE *, const char *, unsigned long);
@@ -770,6 +732,42 @@  static rtx arc_legitimize_address_0 (rtx, rtx, machine_mode mode);
 
 #include "target-def.h"
 
+TARGET_GNU_ATTRIBUTES (arc_attribute_table,
+{
+ /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
+      affects_type_identity, handler, exclude } */
+  { "interrupt", 1, 1, true, false, false, true,
+    arc_handle_interrupt_attribute, NULL },
+  /* Function calls made to this symbol must be done indirectly, because
+     it may lie outside of the 21/25 bit addressing range of a normal function
+     call.  */
+  { "long_call",    0, 0, false, true,  true,  false, NULL, NULL },
+  /* Whereas these functions are always known to reside within the 25 bit
+     addressing range of unconditionalized bl.  */
+  { "medium_call",   0, 0, false, true,  true, false, NULL, NULL },
+  /* And these functions are always known to reside within the 21 bit
+     addressing range of blcc.  */
+  { "short_call",   0, 0, false, true,  true,  false, NULL, NULL },
+  /* Function which are not having the prologue and epilogue generated
+     by the compiler.  */
+  { "naked", 0, 0, true, false, false,  false, arc_handle_fndecl_attribute,
+    NULL },
+  /* Functions calls made using jli instruction.  The pointer in JLI
+     table is found latter.  */
+  { "jli_always",    0, 0, false, true,  true, false,  NULL, NULL },
+  /* Functions calls made using jli instruction.  The pointer in JLI
+     table is given as input parameter.  */
+  { "jli_fixed",    1, 1, false, true,  true, false, arc_handle_jli_attribute,
+    NULL },
+  /* Call a function using secure-mode.  */
+  { "secure_call",  1, 1, false, true, true, false, arc_handle_secure_attribute,
+    NULL },
+   /* Bypass caches using .di flag.  */
+  { "uncached", 0, 0, false, true, false, false, arc_handle_uncached_attribute,
+    NULL },
+  { "aux", 0, 1, true, false, false, false, arc_handle_aux_attribute, NULL }
+});
+
 #undef TARGET_ASM_ALIGNED_HI_OP
 #define TARGET_ASM_ALIGNED_HI_OP "\t.hword\t"
 #undef TARGET_ASM_ALIGNED_SI_OP
diff --git a/gcc/config/arm/arm.cc b/gcc/config/arm/arm.cc
index 620ef7bfb2f..ed2459128ff 100644
--- a/gcc/config/arm/arm.cc
+++ b/gcc/config/arm/arm.cc
@@ -332,7 +332,7 @@  static rtx_insn *thumb1_md_asm_adjust (vec<rtx> &, vec<rtx> &,
 static const char *arm_identify_fpu_from_isa (sbitmap);
 
 /* Table of machine attributes.  */
-static const struct attribute_spec arm_attribute_table[] =
+static const attribute_spec arm_gnu_attributes[] =
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -380,8 +380,17 @@  static const struct attribute_spec arm_attribute_table[] =
     arm_handle_cmse_nonsecure_entry, NULL },
   { "cmse_nonsecure_call", 0, 0, false, false, false, true,
     arm_handle_cmse_nonsecure_call, NULL },
-  { "Advanced SIMD type", 1, 1, false, true, false, true, NULL, NULL },
-  { NULL, 0, 0, false, false, false, false, NULL, NULL }
+  { "Advanced SIMD type", 1, 1, false, true, false, true, NULL, NULL }
+};
+
+static const scoped_attribute_specs arm_gnu_attribute_table =
+{
+  "gnu", arm_gnu_attributes
+};
+
+static const scoped_attribute_specs *const arm_attribute_table[] =
+{
+  &arm_gnu_attribute_table
 };
 
 /* Initialize the GCC target structure.  */
diff --git a/gcc/config/avr/avr.cc b/gcc/config/avr/avr.cc
index 5e0217de36f..59d217efc45 100644
--- a/gcc/config/avr/avr.cc
+++ b/gcc/config/avr/avr.cc
@@ -10442,7 +10442,7 @@  avr_eval_addr_attrib (rtx x)
 
 
 /* AVR attributes.  */
-static const struct attribute_spec avr_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (avr_attribute_table,
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -10467,9 +10467,8 @@  static const struct attribute_spec avr_attribute_table[] =
   { "address",   1, 1, true, false, false,  false,
     avr_handle_addr_attribute, NULL },
   { "absdata",   0, 0, true, false, false,  false,
-    avr_handle_absdata_attribute, NULL },
-  { NULL,        0, 0, false, false, false, false, NULL, NULL }
-};
+    avr_handle_absdata_attribute, NULL }
+});
 
 
 /* Return true if we support address space AS for the architecture in effect
diff --git a/gcc/config/bfin/bfin.cc b/gcc/config/bfin/bfin.cc
index 5718babb6b2..c02136f5e0c 100644
--- a/gcc/config/bfin/bfin.cc
+++ b/gcc/config/bfin/bfin.cc
@@ -4896,7 +4896,7 @@  bfin_handle_l2_attribute (tree *node, tree ARG_UNUSED (name),
 }
 
 /* Table of valid machine attributes.  */
-static const struct attribute_spec bfin_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (bfin_attribute_table,
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -4921,9 +4921,8 @@  static const struct attribute_spec bfin_attribute_table[] =
     bfin_handle_l1_data_attribute, NULL },
   { "l1_data_B", 0, 0, true, false, false, false,
     bfin_handle_l1_data_attribute, NULL },
-  { "l2", 0, 0, true, false, false, false, bfin_handle_l2_attribute, NULL },
-  { NULL, 0, 0, false, false, false, false, NULL, NULL }
-};
+  { "l2", 0, 0, true, false, false, false, bfin_handle_l2_attribute, NULL }
+});
 
 /* Implementation of TARGET_ASM_INTEGER.  When using FD-PIC, we need to
    tell the assembler to generate pointers to function descriptors in
diff --git a/gcc/config/bpf/bpf.cc b/gcc/config/bpf/bpf.cc
index 63637ece78e..c619865f1fc 100644
--- a/gcc/config/bpf/bpf.cc
+++ b/gcc/config/bpf/bpf.cc
@@ -139,7 +139,7 @@  bpf_handle_preserve_access_index_attribute (tree *node, tree name,
 
 /* Target-specific attributes.  */
 
-static const struct attribute_spec bpf_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (bpf_attribute_table,
 {
   /* Syntax: { name, min_len, max_len, decl_required, type_required,
 	       function_type_required, affects_type_identity, handler,
@@ -156,11 +156,8 @@  static const struct attribute_spec bpf_attribute_table[] =
 
  /* Support for `naked' function attribute.  */
  { "naked", 0, 1, false, false, false, false,
-   bpf_handle_fndecl_attribute, NULL },
-
- /* The last attribute spec is set to be NULL.  */
- { NULL,	0,  0, false, false, false, false, NULL, NULL }
-};
+   bpf_handle_fndecl_attribute, NULL }
+});
 
 #undef TARGET_ATTRIBUTE_TABLE
 #define TARGET_ATTRIBUTE_TABLE bpf_attribute_table
diff --git a/gcc/config/csky/csky.cc b/gcc/config/csky/csky.cc
index 731f47cb2c0..ac089feea62 100644
--- a/gcc/config/csky/csky.cc
+++ b/gcc/config/csky/csky.cc
@@ -211,16 +211,15 @@  const int csky_debugger_regno[FIRST_PSEUDO_REGISTER] =
 /* Table of machine attributes.  */
 static tree csky_handle_fndecl_attribute (tree *, tree, tree, int, bool *);
 static tree csky_handle_isr_attribute (tree *, tree, tree, int, bool *);
-static const struct attribute_spec csky_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (csky_attribute_table,
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
   { "naked",	 0, 0, true,  false, false, false, csky_handle_fndecl_attribute, NULL },
   /* Interrupt Service Routines have special prologue and epilogue requirements.  */
   { "interrupt", 0, 1, false, false, false, false, csky_handle_isr_attribute,	 NULL },
-  { "isr",	 0, 1, false, false, false, false, csky_handle_isr_attribute,	 NULL },
-  { NULL,	 0, 0, false, false, false, false, NULL,			 NULL }
-};
+  { "isr",	 0, 1, false, false, false, false, csky_handle_isr_attribute,	 NULL }
+});
 
 /* A C structure for machine-specific, per-function data.
    This is added to the cfun structure.  */
diff --git a/gcc/config/epiphany/epiphany.cc b/gcc/config/epiphany/epiphany.cc
index 68e748c688e..e10e64de823 100644
--- a/gcc/config/epiphany/epiphany.cc
+++ b/gcc/config/epiphany/epiphany.cc
@@ -458,7 +458,7 @@  epiphany_init_reg_tables (void)
                      They unmask them while calling an interruptible
 		     function, though.  */
 
-static const struct attribute_spec epiphany_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (epiphany_attribute_table,
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -468,9 +468,8 @@  static const struct attribute_spec epiphany_attribute_table[] =
     epiphany_handle_forwarder_attribute, NULL },
   { "long_call",  0, 0, false, true, true, false, NULL, NULL },
   { "short_call", 0, 0, false, true, true, false, NULL, NULL },
-  { "disinterrupt", 0, 0, false, true, true, true, NULL, NULL },
-  { NULL,         0, 0, false, false, false, false, NULL, NULL }
-};
+  { "disinterrupt", 0, 0, false, true, true, true, NULL, NULL }
+});
 
 /* Handle an "interrupt" attribute; arguments as in
    struct attribute_spec.handler.  */
diff --git a/gcc/config/gcn/gcn.cc b/gcc/config/gcn/gcn.cc
index 6a2aaefceca..795a852bb53 100644
--- a/gcc/config/gcn/gcn.cc
+++ b/gcc/config/gcn/gcn.cc
@@ -357,14 +357,12 @@  gcn_handle_amdgpu_hsa_kernel_attribute (tree *node, tree name,
  
    Create target-specific __attribute__ types.  */
 
-static const struct attribute_spec gcn_attribute_table[] = {
+TARGET_GNU_ATTRIBUTES (gcn_attribute_table, {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req, handler,
      affects_type_identity } */
   {"amdgpu_hsa_kernel", 0, GCN_KERNEL_ARG_TYPES, false, true,
-   true, true, gcn_handle_amdgpu_hsa_kernel_attribute, NULL},
-  /* End element.  */
-  {NULL, 0, 0, false, false, false, false, NULL, NULL}
-};
+   true, true, gcn_handle_amdgpu_hsa_kernel_attribute, NULL}
+});
 
 /* }}}  */
 /* {{{ Registers and modes.  */
diff --git a/gcc/config/h8300/h8300.cc b/gcc/config/h8300/h8300.cc
index 4bbb1b711e8..5936cdca177 100644
--- a/gcc/config/h8300/h8300.cc
+++ b/gcc/config/h8300/h8300.cc
@@ -4909,7 +4909,7 @@  h8300_insert_attributes (tree node, tree *attributes)
    tiny_data: This variable lives in the tiny data area and can be
    referenced with 16-bit absolute memory references.  */
 
-static const struct attribute_spec h8300_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (h8300_attribute_table,
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -4926,9 +4926,8 @@  static const struct attribute_spec h8300_attribute_table[] =
   { "eightbit_data",     0, 0, true,  false, false, false,
     h8300_handle_eightbit_data_attribute, NULL },
   { "tiny_data",         0, 0, true,  false, false, false,
-    h8300_handle_tiny_data_attribute, NULL },
-  { NULL,                0, 0, false, false, false, false, NULL, NULL }
-};
+    h8300_handle_tiny_data_attribute, NULL }
+});
 
 
 /* Handle an attribute requiring a FUNCTION_DECL; arguments as in
diff --git a/gcc/config/i386/i386-options.cc b/gcc/config/i386/i386-options.cc
index df7d24352d1..351c777c666 100644
--- a/gcc/config/i386/i386-options.cc
+++ b/gcc/config/i386/i386-options.cc
@@ -3951,7 +3951,7 @@  handle_nodirect_extern_access_attribute (tree *pnode, tree name,
 }
 
 /* Table of valid machine attributes.  */
-const struct attribute_spec ix86_attribute_table[] =
+static const attribute_spec ix86_gnu_attributes[] =
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -4031,10 +4031,12 @@  const struct attribute_spec ix86_attribute_table[] =
   { "cf_check", 0, 0, true, false, false, false,
     ix86_handle_fndecl_attribute, NULL },
   { "nodirect_extern_access", 0, 0, true, false, false, false,
-    handle_nodirect_extern_access_attribute, NULL },
+    handle_nodirect_extern_access_attribute, NULL }
+};
 
-  /* End element.  */
-  { NULL, 0, 0, false, false, false, false, NULL, NULL }
+const scoped_attribute_specs ix86_gnu_attribute_table =
+{
+  "gnu", ix86_gnu_attributes
 };
 
 #include "gt-i386-options.h"
diff --git a/gcc/config/i386/i386-options.h b/gcc/config/i386/i386-options.h
index 68666067fea..6274c594647 100644
--- a/gcc/config/i386/i386-options.h
+++ b/gcc/config/i386/i386-options.h
@@ -82,7 +82,7 @@  void ix86_function_specific_print (FILE *, int,
 				   struct cl_target_option *);
 bool ix86_valid_target_attribute_p (tree, tree, tree, int);
 
-extern const struct attribute_spec ix86_attribute_table[];
+extern const struct scoped_attribute_specs ix86_gnu_attribute_table;
 
 
 #endif  /* GCC_I386_OPTIONS_H */
diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
index 7b72aabf0da..e0eff9f1430 100644
--- a/gcc/config/i386/i386.cc
+++ b/gcc/config/i386/i386.cc
@@ -25832,6 +25832,11 @@  ix86_run_selftests (void)
 
 #endif /* CHECKING_P */
 
+static const scoped_attribute_specs *const ix86_attribute_table[] =
+{
+  &ix86_gnu_attribute_table
+};
+
 /* Initialize the GCC target structure.  */
 #undef TARGET_RETURN_IN_MEMORY
 #define TARGET_RETURN_IN_MEMORY ix86_return_in_memory
diff --git a/gcc/config/ia64/ia64.cc b/gcc/config/ia64/ia64.cc
index c241e1a50fc..f7766c25622 100644
--- a/gcc/config/ia64/ia64.cc
+++ b/gcc/config/ia64/ia64.cc
@@ -358,7 +358,7 @@  static bool ia64_expand_vec_perm_const_1 (struct expand_vec_perm_d *d);
 
 
 /* Table of valid machine attributes.  */
-static const struct attribute_spec ia64_attribute_table[] =
+static const attribute_spec ia64_gnu_attributes[] =
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -370,8 +370,17 @@  static const struct attribute_spec ia64_attribute_table[] =
     ia64_vms_common_object_attribute, NULL },
 #endif
   { "version_id",      1, 1, true, false, false, false,
-    ia64_handle_version_id_attribute, NULL },
-  { NULL,	       0, 0, false, false, false, false, NULL, NULL }
+    ia64_handle_version_id_attribute, NULL }
+};
+
+static const scoped_attribute_specs ia64_gnu_attribute_table =
+{
+  "gnu", ia64_gnu_attributes
+};
+
+static const scoped_attribute_specs *const ia64_attribute_table[] =
+{
+  &ia64_gnu_attribute_table
 };
 
 /* Initialize the GCC target structure.  */
diff --git a/gcc/config/loongarch/loongarch.cc b/gcc/config/loongarch/loongarch.cc
index c782f571abc..bf73ab60072 100644
--- a/gcc/config/loongarch/loongarch.cc
+++ b/gcc/config/loongarch/loongarch.cc
@@ -7812,15 +7812,13 @@  loongarch_handle_model_attribute (tree *node, tree name, tree arg, int,
   return NULL_TREE;
 }
 
-static const struct attribute_spec loongarch_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (loongarch_attribute_table,
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
   { "model", 1, 1, true, false, false, false,
-    loongarch_handle_model_attribute, NULL },
-  /* The last attribute spec is set to be NULL.  */
-  {}
-};
+    loongarch_handle_model_attribute, NULL }
+});
 
 bool
 loongarch_use_anchors_for_symbol_p (const_rtx symbol)
diff --git a/gcc/config/m32c/m32c.cc b/gcc/config/m32c/m32c.cc
index e18efc3c7f2..c63c75a6709 100644
--- a/gcc/config/m32c/m32c.cc
+++ b/gcc/config/m32c/m32c.cc
@@ -2999,7 +2999,7 @@  current_function_special_page_vector (rtx x)
 
 #undef TARGET_ATTRIBUTE_TABLE
 #define TARGET_ATTRIBUTE_TABLE m32c_attribute_table
-static const struct attribute_spec m32c_attribute_table[] = {
+TARGET_GNU_ATTRIBUTES (m32c_attribute_table, {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
   { "interrupt", 0, 0, false, false, false, false, interrupt_handler, NULL },
@@ -3007,9 +3007,8 @@  static const struct attribute_spec m32c_attribute_table[] = {
   { "fast_interrupt", 0, 0, false, false, false, false,
     interrupt_handler, NULL },
   { "function_vector", 1, 1, true,  false, false, false,
-    function_vector_handler, NULL },
-  { NULL, 0, 0, false, false, false, false, NULL, NULL }
-};
+    function_vector_handler, NULL }
+});
 
 #undef TARGET_COMP_TYPE_ATTRIBUTES
 #define TARGET_COMP_TYPE_ATTRIBUTES m32c_comp_type_attributes
diff --git a/gcc/config/m32r/m32r.cc b/gcc/config/m32r/m32r.cc
index 63a1798da3d..1a9c8ef1391 100644
--- a/gcc/config/m32r/m32r.cc
+++ b/gcc/config/m32r/m32r.cc
@@ -112,15 +112,14 @@  static HOST_WIDE_INT m32r_starting_frame_offset (void);
 
 /* M32R specific attributes.  */
 
-static const struct attribute_spec m32r_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (m32r_attribute_table,
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
   { "interrupt", 0, 0, true,  false, false, false, NULL, NULL },
   { "model",     1, 1, true,  false, false, false, m32r_handle_model_attribute,
-    NULL },
-  { NULL,        0, 0, false, false, false, false, NULL, NULL }
-};
+    NULL }
+});
 
 /* Initialize the GCC target structure.  */
 #undef  TARGET_ATTRIBUTE_TABLE
diff --git a/gcc/config/m68k/m68k.cc b/gcc/config/m68k/m68k.cc
index 145a92d8710..001cf5bd997 100644
--- a/gcc/config/m68k/m68k.cc
+++ b/gcc/config/m68k/m68k.cc
@@ -361,7 +361,7 @@  static void m68k_asm_final_postscan_insn (FILE *, rtx_insn *insn, rtx [], int);
 #undef TARGET_ASM_FINAL_POSTSCAN_INSN
 #define TARGET_ASM_FINAL_POSTSCAN_INSN m68k_asm_final_postscan_insn
 
-static const struct attribute_spec m68k_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (m68k_attribute_table,
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -370,9 +370,8 @@  static const struct attribute_spec m68k_attribute_table[] =
   { "interrupt_handler", 0, 0, true,  false, false, false,
     m68k_handle_fndecl_attribute, NULL },
   { "interrupt_thread", 0, 0, true,  false, false, false,
-    m68k_handle_fndecl_attribute, NULL },
-  { NULL, 0, 0, false, false, false, false, NULL, NULL }
-};
+    m68k_handle_fndecl_attribute, NULL }
+});
 
 struct gcc_target targetm = TARGET_INITIALIZER;
 
diff --git a/gcc/config/mcore/mcore.cc b/gcc/config/mcore/mcore.cc
index 6f1d7af7937..ca672547494 100644
--- a/gcc/config/mcore/mcore.cc
+++ b/gcc/config/mcore/mcore.cc
@@ -151,16 +151,15 @@  static bool	  mcore_modes_tieable_p		(machine_mode, machine_mode);
 
 /* MCore specific attributes.  */
 
-static const struct attribute_spec mcore_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (mcore_attribute_table,
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
   { "dllexport", 0, 0, true,  false, false, false, NULL, NULL },
   { "dllimport", 0, 0, true,  false, false, false, NULL, NULL },
   { "naked",     0, 0, true,  false, false, false,
-    mcore_handle_naked_attribute, NULL },
-  { NULL,        0, 0, false, false, false, false, NULL, NULL }
-};
+    mcore_handle_naked_attribute, NULL }
+});
 
 /* Initialize the GCC target structure.  */
 #undef  TARGET_ASM_EXTERNAL_LIBCALL
diff --git a/gcc/config/microblaze/microblaze.cc b/gcc/config/microblaze/microblaze.cc
index 60ad55120d2..3ea177b835e 100644
--- a/gcc/config/microblaze/microblaze.cc
+++ b/gcc/config/microblaze/microblaze.cc
@@ -218,15 +218,14 @@  int break_handler;
 int fast_interrupt;
 int save_volatiles;
 
-const struct attribute_spec microblaze_attribute_table[] = {
+TARGET_GNU_ATTRIBUTES (microblaze_attribute_table, {
   /* name         min_len, max_len, decl_req, type_req, fn_type_req,
      affects_type_identity, handler, exclude */
   {"interrupt_handler",	0,       0,    true, false, false, false, NULL, NULL },
   {"break_handler",	0,       0,    true, false, false, false, NULL, NULL },
   {"fast_interrupt",	0,       0,    true, false, false, false, NULL, NULL },
-  {"save_volatiles",	0,       0,    true, false, false, false, NULL, NULL },
-  { NULL,        	0,       0,   false, false, false, false, NULL, NULL }
-};
+  {"save_volatiles",	0,       0,    true, false, false, false, NULL, NULL }
+});
 
 static int microblaze_interrupt_function_p (tree);
 
diff --git a/gcc/config/mips/mips.cc b/gcc/config/mips/mips.cc
index a304e1c5637..0cf49c13bf4 100644
--- a/gcc/config/mips/mips.cc
+++ b/gcc/config/mips/mips.cc
@@ -611,7 +611,7 @@  static tree mips_handle_use_shadow_register_set_attr (tree *, tree, tree, int,
 						      bool *);
 
 /* The value of TARGET_ATTRIBUTE_TABLE.  */
-static const struct attribute_spec mips_attribute_table[] = {
+TARGET_GNU_ATTRIBUTES (mips_attribute_table, {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
   { "long_call",   0, 0, false, true,  true,  false, NULL, NULL },
@@ -636,9 +636,8 @@  static const struct attribute_spec mips_attribute_table[] = {
     mips_handle_use_shadow_register_set_attr, NULL },
   { "keep_interrupts_masked",	0, 0, false, true,  true, false, NULL, NULL },
   { "use_debug_exception_return", 0, 0, false, true, true, false, NULL, NULL },
-  { "use_hazard_barrier_return", 0, 0, true, false, false, false, NULL, NULL },
-  { NULL,	   0, 0, false, false, false, false, NULL, NULL }
-};
+  { "use_hazard_barrier_return", 0, 0, true, false, false, false, NULL, NULL }
+});
 
 /* A table describing all the processors GCC knows about; see
    mips-cpus.def for details.  */
diff --git a/gcc/config/msp430/msp430.cc b/gcc/config/msp430/msp430.cc
index 061a9c77961..85f499f175d 100644
--- a/gcc/config/msp430/msp430.cc
+++ b/gcc/config/msp430/msp430.cc
@@ -2057,7 +2057,7 @@  static const struct attribute_spec::exclusions attr_either_exclusions[] =
 #define TARGET_ATTRIBUTE_TABLE		msp430_attribute_table
 
 /* Table of MSP430-specific attributes.  */
-const struct attribute_spec msp430_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (msp430_attribute_table,
   {
     /* { name, min_num_args, max_num_args, decl_req, type_req, fn_type_req,
 	 affects_type_identity, handler, exclude } */
@@ -2075,10 +2075,8 @@  const struct attribute_spec msp430_attribute_table[] =
     { ATTR_UPPER,       0, 0, true,  false, false, false, msp430_section_attr,
       attr_upper_exclusions },
     { ATTR_EITHER,      0, 0, true,  false, false, false, msp430_section_attr,
-      attr_either_exclusions },
-
-    { NULL,		0, 0, false, false, false, false, NULL,  NULL }
-  };
+      attr_either_exclusions }
+  });
 
 #undef TARGET_HANDLE_GENERIC_ATTRIBUTE
 #define TARGET_HANDLE_GENERIC_ATTRIBUTE msp430_handle_generic_attribute
diff --git a/gcc/config/nds32/nds32.cc b/gcc/config/nds32/nds32.cc
index 1f8de2a514a..e0a73985b66 100644
--- a/gcc/config/nds32/nds32.cc
+++ b/gcc/config/nds32/nds32.cc
@@ -288,7 +288,7 @@  static const int nds32_reg_alloc_order_for_speed[] =
 };
 
 /* Defining target-specific uses of __attribute__.  */
-static const struct attribute_spec nds32_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (nds32_attribute_table,
 {
   /* Syntax: { name, min_len, max_len, decl_required, type_required,
 	       function_type_required, affects_type_identity, handler,
@@ -326,11 +326,8 @@  static const struct attribute_spec nds32_attribute_table[] =
 
   /* FOR BACKWARD COMPATIBILITY,
      this attribute also tells no prologue/epilogue.  */
-  { "no_prologue",  0,  0, false, false, false, false, NULL, NULL },
-
-  /* The last attribute spec is set to be NULL.  */
-  { NULL,           0,  0, false, false, false, false, NULL, NULL }
-};
+  { "no_prologue",  0,  0, false, false, false, false, NULL, NULL }
+});
 
 
 /* ------------------------------------------------------------------------ */
diff --git a/gcc/config/nvptx/nvptx.cc b/gcc/config/nvptx/nvptx.cc
index 634c31673be..d5497631429 100644
--- a/gcc/config/nvptx/nvptx.cc
+++ b/gcc/config/nvptx/nvptx.cc
@@ -5834,16 +5834,15 @@  nvptx_handle_shared_attribute (tree *node, tree name, tree ARG_UNUSED (args),
 }
 
 /* Table of valid machine attributes.  */
-static const struct attribute_spec nvptx_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (nvptx_attribute_table,
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
   { "kernel", 0, 0, true, false,  false, false, nvptx_handle_kernel_attribute,
     NULL },
   { "shared", 0, 0, true, false,  false, false, nvptx_handle_shared_attribute,
-    NULL },
-  { NULL, 0, 0, false, false, false, false, NULL, NULL }
-};
+    NULL }
+});
 
 /* Limit vector alignments to BIGGEST_ALIGNMENT.  */
 
diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
index e36b5fb9bd0..154d80b48e7 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -421,7 +421,7 @@  static tree riscv_handle_type_attribute (tree *, tree, tree, int, bool *);
 static void riscv_legitimize_poly_move (machine_mode, rtx, rtx, rtx);
 
 /* Defining target-specific uses of __attribute__.  */
-static const struct attribute_spec riscv_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (riscv_attribute_table,
 {
   /* Syntax: { name, min_len, max_len, decl_required, type_required,
 	       function_type_required, affects_type_identity, handler,
@@ -437,11 +437,8 @@  static const struct attribute_spec riscv_attribute_table[] =
   /* The following two are used for the built-in properties of the Vector type
      and are not used externally */
   {"RVV sizeless type", 4, 4, false, true, false, true, NULL, NULL},
-  {"RVV type", 0, 0, false, true, false, true, NULL, NULL},
-
-  /* The last attribute spec is set to be NULL.  */
-  { NULL,	0,  0, false, false, false, false, NULL, NULL }
-};
+  {"RVV type", 0, 0, false, true, false, true, NULL, NULL}
+});
 
 /* Order for the CLOBBERs/USEs of gpr_save.  */
 static const unsigned gpr_save_reg_order[] = {
diff --git a/gcc/config/rl78/rl78.cc b/gcc/config/rl78/rl78.cc
index 0cbd6bf780a..b2e9b04b691 100644
--- a/gcc/config/rl78/rl78.cc
+++ b/gcc/config/rl78/rl78.cc
@@ -898,7 +898,7 @@  rl78_handle_vector_attribute (tree * node,
 #define TARGET_ATTRIBUTE_TABLE		rl78_attribute_table
 
 /* Table of RL78-specific attributes.  */
-const struct attribute_spec rl78_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (rl78_attribute_table,
 {
   /* Name, min_len, max_len, decl_req, type_req, fn_type_req,
      affects_type_identity, handler, exclude.  */
@@ -911,9 +911,8 @@  const struct attribute_spec rl78_attribute_table[] =
   { "saddr",          0, 0, true, false, false, false,
     rl78_handle_saddr_attribute, NULL },
   { "vector",         1, -1, true, false, false, false,
-	rl78_handle_vector_attribute, NULL },
-  { NULL,             0, 0, false, false, false, false, NULL, NULL }
-};
+	rl78_handle_vector_attribute, NULL }
+});
 
 
 
diff --git a/gcc/config/rs6000/rs6000.cc b/gcc/config/rs6000/rs6000.cc
index 5f56c3ed85b..cc3f97ae4c5 100644
--- a/gcc/config/rs6000/rs6000.cc
+++ b/gcc/config/rs6000/rs6000.cc
@@ -1255,7 +1255,7 @@  static const char alt_reg_names[][8] =
 
 /* Table of valid machine attributes.  */
 
-static const struct attribute_spec rs6000_attribute_table[] =
+static const attribute_spec rs6000_gnu_attributes[] =
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -1272,7 +1272,16 @@  static const struct attribute_spec rs6000_attribute_table[] =
 #ifdef SUBTARGET_ATTRIBUTE_TABLE
   SUBTARGET_ATTRIBUTE_TABLE,
 #endif
-  { NULL,        0, 0, false, false, false, false, NULL, NULL }
+};
+
+static const scoped_attribute_specs rs6000_gnu_attribute_table =
+{
+  "gnu", rs6000_gnu_attributes
+};
+
+static const scoped_attribute_specs *const rs6000_attribute_table[] =
+{
+  &rs6000_gnu_attribute_table
 };
 
 #ifndef TARGET_PROFILE_KERNEL
diff --git a/gcc/config/rx/rx.cc b/gcc/config/rx/rx.cc
index 245c6a4413d..0754e286552 100644
--- a/gcc/config/rx/rx.cc
+++ b/gcc/config/rx/rx.cc
@@ -2760,7 +2760,7 @@  rx_handle_vector_attribute (tree * node,
 }
 
 /* Table of RX specific attributes.  */
-const struct attribute_spec rx_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (rx_attribute_table,
 {
   /* Name, min_len, max_len, decl_req, type_req, fn_type_req,
      affects_type_identity, handler, exclude.  */
@@ -2771,9 +2771,8 @@  const struct attribute_spec rx_attribute_table[] =
   { "naked",          0, 0, true, false, false, false,
     rx_handle_func_attribute, NULL },
   { "vector",         1, -1, true, false, false, false,
-    rx_handle_vector_attribute, NULL },
-  { NULL,             0, 0, false, false, false, false, NULL, NULL }
-};
+    rx_handle_vector_attribute, NULL }
+});
 
 /* Implement TARGET_OVERRIDE_OPTIONS_AFTER_CHANGE.  */
 
diff --git a/gcc/config/s390/s390.cc b/gcc/config/s390/s390.cc
index 64f56d8effa..a8205385451 100644
--- a/gcc/config/s390/s390.cc
+++ b/gcc/config/s390/s390.cc
@@ -1303,7 +1303,7 @@  s390_handle_string_attribute (tree *node, tree name ATTRIBUTE_UNUSED,
   return NULL_TREE;
 }
 
-static const struct attribute_spec s390_attribute_table[] = {
+TARGET_GNU_ATTRIBUTES (s390_attribute_table, {
   { "hotpatch", 2, 2, true, false, false, false,
     s390_handle_hotpatch_attribute, NULL },
   { "s390_vector_bool", 0, 0, false, true, false, true,
@@ -1319,11 +1319,8 @@  static const struct attribute_spec s390_attribute_table[] = {
   { "function_return_reg", 1, 1, true, false, false, false,
     s390_handle_string_attribute, NULL },
   { "function_return_mem", 1, 1, true, false, false, false,
-    s390_handle_string_attribute, NULL },
-
-  /* End element.  */
-  { NULL,        0, 0, false, false, false, false, NULL, NULL }
-};
+    s390_handle_string_attribute, NULL }
+});
 
 /* Return the alignment for LABEL.  We default to the -falign-labels
    value except for the literal pool base label.  */
diff --git a/gcc/config/sh/sh.cc b/gcc/config/sh/sh.cc
index 6ec2eecf754..8c378b28b6d 100644
--- a/gcc/config/sh/sh.cc
+++ b/gcc/config/sh/sh.cc
@@ -329,7 +329,7 @@  static bool sh_hard_regno_mode_ok (unsigned int, machine_mode);
 static bool sh_modes_tieable_p (machine_mode, machine_mode);
 static bool sh_can_change_mode_class (machine_mode, machine_mode, reg_class_t);
 
-static const struct attribute_spec sh_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (sh_attribute_table,
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -348,9 +348,8 @@  static const struct attribute_spec sh_attribute_table[] =
   { "resbank",           0, 0, true,  false, false, false,
     sh_handle_resbank_handler_attribute, NULL },
   { "function_vector",   1, 1, true,  false, false, false,
-    sh2a_handle_function_vector_handler_attribute, NULL },
-  { NULL,                0, 0, false, false, false, false, NULL, NULL }
-};
+    sh2a_handle_function_vector_handler_attribute, NULL }
+});
 
 /* Initialize the GCC target structure.  */
 #undef TARGET_ATTRIBUTE_TABLE
diff --git a/gcc/config/sparc/sparc.cc b/gcc/config/sparc/sparc.cc
index 82e57952414..a78dedc8075 100644
--- a/gcc/config/sparc/sparc.cc
+++ b/gcc/config/sparc/sparc.cc
@@ -721,13 +721,12 @@  static HARD_REG_SET sparc_zero_call_used_regs (HARD_REG_SET);
 
 #ifdef SUBTARGET_ATTRIBUTE_TABLE
 /* Table of valid machine attributes.  */
-static const struct attribute_spec sparc_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (sparc_attribute_table,
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        do_diagnostic, handler, exclude } */
-  SUBTARGET_ATTRIBUTE_TABLE,
-  { NULL,        0, 0, false, false, false, false, NULL, NULL }
-};
+  SUBTARGET_ATTRIBUTE_TABLE
+});
 #endif
 
 char sparc_hard_reg_printed[8];
diff --git a/gcc/config/stormy16/stormy16.cc b/gcc/config/stormy16/stormy16.cc
index 10887153906..071043b4128 100644
--- a/gcc/config/stormy16/stormy16.cc
+++ b/gcc/config/stormy16/stormy16.cc
@@ -2377,7 +2377,7 @@  static tree xstormy16_handle_interrupt_attribute
 static tree xstormy16_handle_below100_attribute
   (tree *, tree, tree, int, bool *);
 
-static const struct attribute_spec xstormy16_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (xstormy16_attribute_table,
 {
   /* name, min_len, max_len, decl_req, type_req, fn_type_req,
      affects_type_identity, handler, exclude.  */
@@ -2386,9 +2386,8 @@  static const struct attribute_spec xstormy16_attribute_table[] =
   { "BELOW100",  0, 0, false, false, false, false,
     xstormy16_handle_below100_attribute, NULL },
   { "below100",  0, 0, false, false, false, false,
-    xstormy16_handle_below100_attribute, NULL },
-  { NULL,        0, 0, false, false, false, false, NULL, NULL }
-};
+    xstormy16_handle_below100_attribute, NULL }
+});
 
 /* Handle an "interrupt" attribute;
    arguments as in struct attribute_spec.handler.  */
diff --git a/gcc/config/v850/v850.cc b/gcc/config/v850/v850.cc
index 416c2841a5c..50c91c68b8b 100644
--- a/gcc/config/v850/v850.cc
+++ b/gcc/config/v850/v850.cc
@@ -3114,7 +3114,7 @@  v850_adjust_insn_length (rtx_insn *insn, int length)
 
 /* V850 specific attributes.  */
 
-static const struct attribute_spec v850_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (v850_attribute_table,
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -3127,9 +3127,8 @@  static const struct attribute_spec v850_attribute_table[] =
   { "tda",               0, 0, true,  false, false, false,
     v850_handle_data_area_attribute, NULL },
   { "zda",               0, 0, true,  false, false, false,
-    v850_handle_data_area_attribute, NULL },
-  { NULL,                0, 0, false, false, false, false, NULL, NULL }
-};
+    v850_handle_data_area_attribute, NULL }
+});
 
 static void
 v850_option_override (void)
diff --git a/gcc/config/visium/visium.cc b/gcc/config/visium/visium.cc
index 5fadbc80be0..4a1877c2ac1 100644
--- a/gcc/config/visium/visium.cc
+++ b/gcc/config/visium/visium.cc
@@ -145,14 +145,13 @@  static inline bool current_function_has_lr_slot (void);
 
 /* Supported attributes:
    interrupt -- specifies this function is an interrupt handler.   */
-static const struct attribute_spec visium_attribute_table[] =
+TARGET_GNU_ATTRIBUTES (visium_attribute_table,
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
   { "interrupt", 0, 0, true, false, false, false, visium_handle_interrupt_attr,
-    NULL},
-  { NULL, 0, 0, false, false, false, false, NULL, NULL },
-};
+    NULL}
+});
 
 static struct machine_function *visium_init_machine_status (void);
 
diff --git a/gcc/cp/cp-objcp-common.h b/gcc/cp/cp-objcp-common.h
index 1408301a300..d3fa002593f 100644
--- a/gcc/cp/cp-objcp-common.h
+++ b/gcc/cp/cp-objcp-common.h
@@ -122,13 +122,16 @@  extern tree cxx_simulate_record_decl (location_t, const char *,
 #undef LANG_HOOKS_FINALIZE_EARLY_DEBUG
 #define LANG_HOOKS_FINALIZE_EARLY_DEBUG c_common_finalize_early_debug
 
-/* Attribute hooks.  */
-#undef LANG_HOOKS_COMMON_ATTRIBUTE_TABLE
-#define LANG_HOOKS_COMMON_ATTRIBUTE_TABLE c_common_attribute_table
-#undef LANG_HOOKS_FORMAT_ATTRIBUTE_TABLE
-#define LANG_HOOKS_FORMAT_ATTRIBUTE_TABLE c_common_format_attribute_table
+static const scoped_attribute_specs *const cp_objcp_attribute_table[] =
+{
+  &std_attribute_table,
+  &cxx_gnu_attribute_table,
+  &c_common_gnu_attribute_table,
+  &c_common_format_attribute_table
+};
+
 #undef LANG_HOOKS_ATTRIBUTE_TABLE
-#define LANG_HOOKS_ATTRIBUTE_TABLE cxx_attribute_table
+#define LANG_HOOKS_ATTRIBUTE_TABLE cp_objcp_attribute_table
 
 #undef LANG_HOOKS_TREE_INLINING_VAR_MOD_TYPE_P
 #define LANG_HOOKS_TREE_INLINING_VAR_MOD_TYPE_P cp_var_mod_type_p
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 98b29e9cf81..0a543c5f14c 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -8000,7 +8000,8 @@  extern tree maybe_dummy_object			(tree, tree *);
 extern bool is_dummy_object			(const_tree);
 extern bool is_byte_access_type			(tree);
 extern bool is_byte_access_type_not_plain_char	(tree);
-extern const struct attribute_spec cxx_attribute_table[];
+extern const struct scoped_attribute_specs cxx_gnu_attribute_table;
+extern const struct scoped_attribute_specs std_attribute_table;
 extern tree make_ptrmem_cst			(tree, tree);
 extern tree cp_build_type_attribute_variant     (tree, tree);
 extern tree cp_build_reference_type		(tree, bool);
diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
index 417c92ba76f..fd99d64ca47 100644
--- a/gcc/cp/tree.cc
+++ b/gcc/cp/tree.cc
@@ -5072,7 +5072,7 @@  handle_likeliness_attribute (tree *node, tree name, tree args,
 }
 
 /* Table of valid C++ attributes.  */
-const struct attribute_spec cxx_attribute_table[] =
+static const attribute_spec cxx_gnu_attributes[] =
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -5080,11 +5080,15 @@  const struct attribute_spec cxx_attribute_table[] =
     handle_init_priority_attribute, NULL },
   { "abi_tag", 1, -1, false, false, false, true,
     handle_abi_tag_attribute, NULL },
-  { NULL, 0, 0, false, false, false, false, NULL, NULL }
+};
+
+const scoped_attribute_specs cxx_gnu_attribute_table =
+{
+  "gnu", cxx_gnu_attributes
 };
 
 /* Table of C++ standard attributes.  */
-const struct attribute_spec std_attribute_table[] =
+static const attribute_spec std_attributes[] =
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -5105,10 +5109,11 @@  const struct attribute_spec std_attribute_table[] =
   { "pre", 0, -1, false, false, false, false,
     handle_contract_attribute, NULL },
   { "post", 0, -1, false, false, false, false,
-    handle_contract_attribute, NULL },
-  { NULL, 0, 0, false, false, false, false, NULL, NULL }
+    handle_contract_attribute, NULL }
 };
 
+const scoped_attribute_specs std_attribute_table = { nullptr, std_attributes };
+
 /* Handle an "init_priority" attribute; arguments as in
    struct attribute_spec.handler.  */
 static tree
@@ -5704,7 +5709,6 @@  void
 init_tree (void)
 {
   list_hash_table = hash_table<list_hasher>::create_ggc (61);
-  register_scoped_attributes (std_attribute_table, NULL);
 }
 
 /* Returns the kind of special function that DECL (a FUNCTION_DECL)
diff --git a/gcc/d/d-attribs.cc b/gcc/d/d-attribs.cc
index c0dc0e24ded..f6411058072 100644
--- a/gcc/d/d-attribs.cc
+++ b/gcc/d/d-attribs.cc
@@ -162,7 +162,7 @@  extern const struct attribute_spec::exclusions attr_cold_hot_exclusions[] =
 
 /* Table of machine-independent attributes.
    For internal use (marking of built-ins) only.  */
-const attribute_spec d_langhook_common_attribute_table[] =
+static const attribute_spec d_langhook_common_attributes[] =
 {
   ATTR_SPEC ("noreturn", 0, 0, true, false, false, false,
 	     handle_noreturn_attribute, attr_noreturn_exclusions),
@@ -190,11 +190,15 @@  const attribute_spec d_langhook_common_attribute_table[] =
 	     handle_fnspec_attribute, NULL),
   ATTR_SPEC ("omp declare simd", 0, -1, true,  false, false, false,
 	     handle_omp_declare_simd_attribute, NULL),
-  ATTR_SPEC (NULL, 0, 0, false, false, false, false, NULL, NULL),
+};
+
+const scoped_attribute_specs d_langhook_common_attribute_table =
+{
+  "gnu", d_langhook_common_attributes
 };
 
 /* Table of D language attributes exposed by `gcc.attribute' UDAs.  */
-const attribute_spec d_langhook_attribute_table[] =
+static const attribute_spec d_langhook_gnu_attributes[] =
 {
   ATTR_SPEC ("noinline", 0, 0, true, false, false, false,
 	     d_handle_noinline_attribute, attr_noinline_exclusions),
@@ -238,9 +242,12 @@  const attribute_spec d_langhook_attribute_table[] =
 	     d_handle_used_attribute, NULL),
   ATTR_SPEC ("visibility", 1, 1, false, false, false, false,
 	     d_handle_visibility_attribute, NULL),
-  ATTR_SPEC (NULL, 0, 0, false, false, false, false, NULL, NULL),
 };
 
+const scoped_attribute_specs d_langhook_gnu_attribute_table =
+{
+  "gnu", d_langhook_gnu_attributes
+};
 
 /* Insert the type attribute ATTRNAME with value VALUE into TYPE.
    Returns a new variant of the original type declaration.  */
@@ -283,20 +290,14 @@  uda_attribute_p (const char *name)
 
   /* Search both our language, and target attribute tables.
      Common and format attributes are kept internal.  */
-  for (const attribute_spec *p = d_langhook_attribute_table; p->name; p++)
-    {
-      if (get_identifier (p->name) == ident)
-	return true;
-    }
+  for (const attribute_spec &p : d_langhook_gnu_attributes)
+    if (get_identifier (p.name) == ident)
+      return true;
 
-  if (targetm.attribute_table)
-    {
-      for (const attribute_spec *p = targetm.attribute_table; p->name; p++)
-	{
-	  if (get_identifier (p->name) == ident)
-	    return true;
-	}
-    }
+  for (auto scoped_attributes : targetm.attribute_table)
+    for (const attribute_spec &p : scoped_attributes->attributes)
+      if (get_identifier (p.name) == ident)
+	return true;
 
   return false;
 }
diff --git a/gcc/d/d-lang.cc b/gcc/d/d-lang.cc
index 61fc1608b40..dcbffec3752 100644
--- a/gcc/d/d-lang.cc
+++ b/gcc/d/d-lang.cc
@@ -1927,6 +1927,12 @@  d_get_sarif_source_language (const char *)
   return "d";
 }
 
+const scoped_attribute_specs *const d_langhook_attribute_table[] =
+{
+  &d_langhook_gnu_attribute_table,
+  &d_langhook_common_attribute_table,
+};
+
 /* Definitions for our language-specific hooks.  */
 
 #undef LANG_HOOKS_NAME
@@ -1938,7 +1944,6 @@  d_get_sarif_source_language (const char *)
 #undef LANG_HOOKS_HANDLE_OPTION
 #undef LANG_HOOKS_POST_OPTIONS
 #undef LANG_HOOKS_PARSE_FILE
-#undef LANG_HOOKS_COMMON_ATTRIBUTE_TABLE
 #undef LANG_HOOKS_ATTRIBUTE_TABLE
 #undef LANG_HOOKS_GET_ALIAS_SET
 #undef LANG_HOOKS_TYPES_COMPATIBLE_P
@@ -1971,7 +1976,6 @@  d_get_sarif_source_language (const char *)
 #define LANG_HOOKS_HANDLE_OPTION	    d_handle_option
 #define LANG_HOOKS_POST_OPTIONS		    d_post_options
 #define LANG_HOOKS_PARSE_FILE		    d_parse_file
-#define LANG_HOOKS_COMMON_ATTRIBUTE_TABLE   d_langhook_common_attribute_table
 #define LANG_HOOKS_ATTRIBUTE_TABLE	    d_langhook_attribute_table
 #define LANG_HOOKS_GET_ALIAS_SET	    d_get_alias_set
 #define LANG_HOOKS_TYPES_COMPATIBLE_P	    d_types_compatible_p
diff --git a/gcc/d/d-tree.h b/gcc/d/d-tree.h
index d19c3f50bd9..46a28737daa 100644
--- a/gcc/d/d-tree.h
+++ b/gcc/d/d-tree.h
@@ -520,8 +520,8 @@  extern tree insert_decl_attribute (tree, const char *, tree = NULL_TREE);
 extern void apply_user_attributes (Dsymbol *, tree);
 
 /* In d-builtins.cc.  */
-extern const attribute_spec d_langhook_attribute_table[];
-extern const attribute_spec d_langhook_common_attribute_table[];
+extern const struct scoped_attribute_specs d_langhook_gnu_attribute_table;
+extern const struct scoped_attribute_specs d_langhook_common_attribute_table;
 extern Type *build_frontend_type (tree);
 
 extern tree d_builtin_function (tree);
diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index d83ca73b1af..abc23b2bb73 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -10528,12 +10528,33 @@  Target-specific attributes may be defined for functions, data and types.
 These are described using the following target hooks; they also need to
 be documented in @file{extend.texi}.
 
-@deftypevr {Target Hook} {const struct attribute_spec *} TARGET_ATTRIBUTE_TABLE
-If defined, this target hook points to an array of @samp{struct
-attribute_spec} (defined in @file{tree-core.h}) specifying the machine
-specific attributes for this target and some of the restrictions on the
-entities to which these attributes are applied and the arguments they
-take.
+@deftypevr {Target Hook} {array_slice<const struct scoped_attribute_specs *const>} TARGET_ATTRIBUTE_TABLE
+If defined, this target hook provides an array of
+@samp{scoped_attribute_spec}s (defined in @file{attribs.h}) that specify the
+machine-specific attributes for this target.  The information includes some
+of the restrictions on the entities to which these attributes are applied
+and the arguments that the attributes take.
+
+In C and C++, these attributes are associated with two syntaxes:
+the traditional GNU @code{__attribute__} syntax and the standard
+@samp{[[]]} syntax.  Attributes that support the GNU syntax must be
+placed in the @code{gnu} namespace.  Such attributes can then also be
+written @samp{[[gnu::@dots{}]]}.  Attributes that use only the standard
+syntax should be placed in whichever namespace the attribute specification
+requires.  For example, a target might choose to support vendor-specific
+@samp{[[]]} attributes that the vendor places in their own namespace.
+
+Targets that only define attributes in the @code{gnu} namespace
+can uase the following shorthand to define the table:
+
+@smallexample
+TARGET_GNU_ATTRIBUTES (@var{cpu_attribute_table}, @{
+  @{ "@var{attribute1}", @dots{} @},
+  @{ "@var{attribute2}", @dots{} @},
+  @dots{},
+  @{ "@var{attributen}", @dots{} @},
+@});
+@end smallexample
 @end deftypevr
 
 @deftypefn {Target Hook} bool TARGET_ATTRIBUTE_TAKES_IDENTIFIER_P (const_tree @var{name})
diff --git a/gcc/fortran/f95-lang.cc b/gcc/fortran/f95-lang.cc
index 350e6e379eb..99dd76226a2 100644
--- a/gcc/fortran/f95-lang.cc
+++ b/gcc/fortran/f95-lang.cc
@@ -39,6 +39,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "cpp.h"
 #include "trans-types.h"
 #include "trans-const.h"
+#include "attribs.h"
 
 /* Language-dependent contents of an identifier.  */
 
@@ -87,7 +88,7 @@  gfc_handle_omp_declare_target_attribute (tree *, tree, tree, int, bool *)
 }
 
 /* Table of valid Fortran attributes.  */
-static const struct attribute_spec gfc_attribute_table[] =
+static const attribute_spec gfc_gnu_attributes[] =
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -97,7 +98,16 @@  static const struct attribute_spec gfc_attribute_table[] =
     gfc_handle_omp_declare_target_attribute, NULL },
   { "oacc function", 0, -1, true,  false, false, false,
     gfc_handle_omp_declare_target_attribute, NULL },
-  { NULL,		  0, 0, false, false, false, false, NULL, NULL }
+};
+
+static const scoped_attribute_specs gfc_gnu_attribute_table =
+{
+  "gnu", gfc_gnu_attributes
+};
+
+static const scoped_attribute_specs *const gfc_attribute_table[] =
+{
+  &gfc_gnu_attribute_table
 };
 
 /* Get a value for the SARIF v2.1.0 "artifact.sourceLanguage" property,
diff --git a/gcc/jit/dummy-frontend.cc b/gcc/jit/dummy-frontend.cc
index a729086bafb..61cc0e1ae66 100644
--- a/gcc/jit/dummy-frontend.cc
+++ b/gcc/jit/dummy-frontend.cc
@@ -87,7 +87,7 @@  static const struct attribute_spec::exclusions attr_const_pure_exclusions[] =
 };
 
 /* Table of machine-independent attributes supported in libgccjit.  */
-const struct attribute_spec jit_attribute_table[] =
+static const attribute_spec jit_gnu_attributes[] =
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -128,22 +128,36 @@  const struct attribute_spec jit_attribute_table[] =
   /* For internal use only.  The leading '*' both prevents its usage in
      source code and signals that it may be overridden by machine tables.  */
   { "*tm regparm",            0, 0, false, true, true, false,
-			      ignore_attribute, NULL },
-  { NULL,                     0, 0, false, false, false, false, NULL, NULL }
+			      ignore_attribute, NULL }
+};
+
+static const scoped_attribute_specs jit_gnu_attribute_table =
+{
+  "gnu", jit_gnu_attributes
 };
 
 /* Give the specifications for the format attributes, used by C and all
    descendants.  */
 
-const struct attribute_spec jit_format_attribute_table[] =
+static const attribute_spec jit_format_attributes[] =
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
   { "format",                 3, 3, false, true,  true, false,
 			      handle_format_attribute, NULL },
   { "format_arg",             1, 1, false, true,  true, false,
-			      handle_format_arg_attribute, NULL },
-  { NULL,                     0, 0, false, false, false, false, NULL, NULL }
+			      handle_format_arg_attribute, NULL }
+};
+
+static const scoped_attribute_specs jit_format_attribute_table =
+{
+  "gnu", jit_format_attributes
+};
+
+static const scoped_attribute_specs *const jit_attribute_table[] =
+{
+  &jit_gnu_attribute_table,
+  &jit_format_attribute_table
 };
 
 /* Attribute handlers.  */
@@ -719,10 +733,8 @@  jit_langhook_getdecls (void)
 #define LANG_HOOKS_GETDECLS		jit_langhook_getdecls
 
 /* Attribute hooks.  */
-#undef LANG_HOOKS_COMMON_ATTRIBUTE_TABLE
-#define LANG_HOOKS_COMMON_ATTRIBUTE_TABLE jit_attribute_table
-#undef LANG_HOOKS_FORMAT_ATTRIBUTE_TABLE
-#define LANG_HOOKS_FORMAT_ATTRIBUTE_TABLE jit_format_attribute_table
+#undef LANG_HOOKS_ATTRIBUTE_TABLE
+#define LANG_HOOKS_ATTRIBUTE_TABLE jit_attribute_table
 
 #undef  LANG_HOOKS_DEEP_UNSHARING
 #define LANG_HOOKS_DEEP_UNSHARING	true
diff --git a/gcc/langhooks-def.h b/gcc/langhooks-def.h
index c6d18526360..c9cb65759c2 100644
--- a/gcc/langhooks-def.h
+++ b/gcc/langhooks-def.h
@@ -153,9 +153,7 @@  extern const char *lhd_get_sarif_source_language (const char *);
 #define LANG_HOOKS_GET_SARIF_SOURCE_LANGUAGE lhd_get_sarif_source_language
 
 /* Attribute hooks.  */
-#define LANG_HOOKS_ATTRIBUTE_TABLE		NULL
-#define LANG_HOOKS_COMMON_ATTRIBUTE_TABLE	NULL
-#define LANG_HOOKS_FORMAT_ATTRIBUTE_TABLE	NULL
+#define LANG_HOOKS_ATTRIBUTE_TABLE		{}
 
 /* Tree inlining hooks.  */
 #define LANG_HOOKS_TREE_INLINING_VAR_MOD_TYPE_P \
@@ -367,8 +365,6 @@  extern void lhd_end_section (void);
   LANG_HOOKS_PRINT_ERROR_FUNCTION, \
   LANG_HOOKS_TO_TARGET_CHARSET, \
   LANG_HOOKS_ATTRIBUTE_TABLE, \
-  LANG_HOOKS_COMMON_ATTRIBUTE_TABLE, \
-  LANG_HOOKS_FORMAT_ATTRIBUTE_TABLE, \
   LANG_HOOKS_TREE_INLINING_INITIALIZER, \
   LANG_HOOKS_TREE_DUMP_INITIALIZER, \
   LANG_HOOKS_DECLS, \
diff --git a/gcc/langhooks.h b/gcc/langhooks.h
index cca75285fc2..2785a0070fc 100644
--- a/gcc/langhooks.h
+++ b/gcc/langhooks.h
@@ -532,9 +532,7 @@  struct lang_hooks
      table of attributes specific to the language, a table of
      attributes common to two or more languages (to allow easy
      sharing), and a table of attributes for checking formats.  */
-  const struct attribute_spec *attribute_table;
-  const struct attribute_spec *common_attribute_table;
-  const struct attribute_spec *format_attribute_table;
+  array_slice<const struct scoped_attribute_specs *const> attribute_table;
 
   struct lang_hooks_for_tree_inlining tree_inlining;
 
diff --git a/gcc/lto/lto-lang.cc b/gcc/lto/lto-lang.cc
index 14d419c2013..a8adf110b20 100644
--- a/gcc/lto/lto-lang.cc
+++ b/gcc/lto/lto-lang.cc
@@ -94,7 +94,7 @@  static const struct attribute_spec::exclusions attr_const_pure_exclusions[] =
 };
 
 /* Table of machine-independent attributes supported in GIMPLE.  */
-const struct attribute_spec lto_attribute_table[] =
+static const attribute_spec lto_gnu_attributes[] =
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -135,14 +135,18 @@  const struct attribute_spec lto_attribute_table[] =
   /* For internal use only.  The leading '*' both prevents its usage in
      source code and signals that it may be overridden by machine tables.  */
   { "*tm regparm",            0, 0, false, true, true, false,
-			      ignore_attribute, NULL },
-  { NULL,                     0, 0, false, false, false, false, NULL, NULL }
+			      ignore_attribute, NULL }
+};
+
+static const scoped_attribute_specs lto_gnu_attribute_table =
+{
+  "gnu", lto_gnu_attributes
 };
 
 /* Give the specifications for the format attributes, used by C and all
    descendants.  */
 
-const struct attribute_spec lto_format_attribute_table[] =
+static const attribute_spec lto_format_attributes[] =
 {
   /* { name, min_len, max_len, decl_req, type_req, fn_type_req,
        affects_type_identity, handler, exclude } */
@@ -150,7 +154,17 @@  const struct attribute_spec lto_format_attribute_table[] =
 			      handle_format_attribute, NULL },
   { "format_arg",             1, 1, false, true,  true, false,
 			      handle_format_arg_attribute, NULL },
-  { NULL,                     0, 0, false, false, false, false, NULL, NULL }
+};
+
+static const scoped_attribute_specs lto_format_attribute_table =
+{
+  "gnu", lto_format_attributes
+};
+
+static const scoped_attribute_specs *const lto_attribute_table[] =
+{
+  &lto_gnu_attribute_table,
+  &lto_format_attribute_table
 };
 
 enum built_in_attribute
@@ -1463,10 +1477,8 @@  static void lto_init_ts (void)
 #define LANG_HOOKS_EH_PERSONALITY lto_eh_personality
 
 /* Attribute hooks.  */
-#undef LANG_HOOKS_COMMON_ATTRIBUTE_TABLE
-#define LANG_HOOKS_COMMON_ATTRIBUTE_TABLE lto_attribute_table
-#undef LANG_HOOKS_FORMAT_ATTRIBUTE_TABLE
-#define LANG_HOOKS_FORMAT_ATTRIBUTE_TABLE lto_format_attribute_table
+#undef LANG_HOOKS_ATTRIBUTE_TABLE
+#define LANG_HOOKS_ATTRIBUTE_TABLE lto_attribute_table
 
 #undef LANG_HOOKS_BEGIN_SECTION
 #define LANG_HOOKS_BEGIN_SECTION lto_obj_begin_section
diff --git a/gcc/plugin.h b/gcc/plugin.h
index ee0a53ec4c9..f306adfb3b9 100644
--- a/gcc/plugin.h
+++ b/gcc/plugin.h
@@ -201,8 +201,7 @@  invoke_plugin_callbacks (int event ATTRIBUTE_UNUSED,
 
 extern void register_attribute (const struct attribute_spec *attr);
 /* The default argument for the third parameter is given in attribs.h.  */
-extern struct scoped_attributes* register_scoped_attributes (const struct attribute_spec *,
-							     const char *,
+extern struct scoped_attributes* register_scoped_attributes (const struct scoped_attribute_spec &,
 							     bool);
 
 #endif /* PLUGIN_H */
diff --git a/gcc/target-def.h b/gcc/target-def.h
index 847698a1e59..d03b039ab24 100644
--- a/gcc/target-def.h
+++ b/gcc/target-def.h
@@ -118,6 +118,20 @@ 
 #define TARGET_FUNCTION_INCOMING_ARG TARGET_FUNCTION_ARG
 #endif
 
+/* Declare a target attribute table called NAME that only has GNU attributes.
+   There should be no null trailing element.  E.g.:
+
+     TARGET_GNU_ATTRIBUTES (aarch64_attribute_table,
+     {
+       { "aarch64_vector_pcs", ... },
+       ...
+     });  */
+
+#define TARGET_GNU_ATTRIBUTES(NAME, ...) \
+  static const attribute_spec NAME##_2[] = __VA_ARGS__; \
+  static const scoped_attribute_specs NAME##_1 = { "gnu", NAME##_2 }; \
+  static const scoped_attribute_specs *const NAME[] = { &NAME##_1 }
+
 #include "target-hooks-def.h"
 
 #include "hooks.h"
diff --git a/gcc/target.def b/gcc/target.def
index 0996da0f71a..06c5d3b5c2e 100644
--- a/gcc/target.def
+++ b/gcc/target.def
@@ -2218,15 +2218,36 @@  merging.",
  merge_type_attributes)
 
 /* Table of machine attributes and functions to handle them.
-   Ignored if NULL.  */
+   Ignored if empty.  */
 DEFHOOKPOD
 (attribute_table,
- "If defined, this target hook points to an array of @samp{struct\n\
-attribute_spec} (defined in @file{tree-core.h}) specifying the machine\n\
-specific attributes for this target and some of the restrictions on the\n\
-entities to which these attributes are applied and the arguments they\n\
-take.",
- const struct attribute_spec *, NULL)
+ "If defined, this target hook provides an array of\n\
+@samp{scoped_attribute_spec}s (defined in @file{attribs.h}) that specify the\n\
+machine-specific attributes for this target.  The information includes some\n\
+of the restrictions on the entities to which these attributes are applied\n\
+and the arguments that the attributes take.\n\
+\n\
+In C and C++, these attributes are associated with two syntaxes:\n\
+the traditional GNU @code{__attribute__} syntax and the standard\n\
+@samp{[[]]} syntax.  Attributes that support the GNU syntax must be\n\
+placed in the @code{gnu} namespace.  Such attributes can then also be\n\
+written @samp{[[gnu::@dots{}]]}.  Attributes that use only the standard\n\
+syntax should be placed in whichever namespace the attribute specification\n\
+requires.  For example, a target might choose to support vendor-specific\n\
+@samp{[[]]} attributes that the vendor places in their own namespace.\n\
+\n\
+Targets that only define attributes in the @code{gnu} namespace\n\
+can uase the following shorthand to define the table:\n\
+\n\
+@smallexample\n\
+TARGET_GNU_ATTRIBUTES (@var{cpu_attribute_table}, @{\n\
+  @{ \"@var{attribute1}\", @dots{} @},\n\
+  @{ \"@var{attribute2}\", @dots{} @},\n\
+  @dots{},\n\
+  @{ \"@var{attributen}\", @dots{} @},\n\
+@});\n\
+@end smallexample",
+ array_slice<const struct scoped_attribute_specs *const>, {})
 
 /* Return true iff attribute NAME expects a plain identifier as its first
    argument.  */
diff --git a/gcc/tree-inline.cc b/gcc/tree-inline.cc
index 00d05102e7a..2831a0368f6 100644
--- a/gcc/tree-inline.cc
+++ b/gcc/tree-inline.cc
@@ -4094,17 +4094,16 @@  inline_forbidden_p (tree fndecl)
 static bool
 function_attribute_inlinable_p (const_tree fndecl)
 {
-  if (targetm.attribute_table)
+  for (auto scoped_attributes : targetm.attribute_table)
     {
       const_tree a;
 
       for (a = DECL_ATTRIBUTES (fndecl); a; a = TREE_CHAIN (a))
 	{
 	  const_tree name = get_attribute_name (a);
-	  int i;
 
-	  for (i = 0; targetm.attribute_table[i].name != NULL; i++)
-	    if (is_attribute_p (targetm.attribute_table[i].name, name))
+	  for (const attribute_spec &attribute : scoped_attributes->attributes)
+	    if (is_attribute_p (attribute.name, name))
 	      return targetm.function_attribute_inlinable_p (fndecl);
 	}
     }