[v3] Add retain attribute to place symbols in SHF_GNU_RETAIN section

Message ID CAMe9rOqMWr=Z6tgZNW7TXaarf_+Q9agWdQER2518pHVEuP6Gug@mail.gmail.com
State New
Headers show
Series
  • [v3] Add retain attribute to place symbols in SHF_GNU_RETAIN section
Related show

Commit Message

Jason Merrill via Gcc-patches Feb. 17, 2021, 5:34 p.m.
On Wed, Feb 17, 2021 at 6:26 AM Jakub Jelinek <jakub@redhat.com> wrote:
>

> On Tue, Feb 16, 2021 at 11:59:21AM -0800, H.J. Lu wrote:

> >       PR target/99113

> >       * common.opt: Add -fgnu-retain.

>

> I'm not sure -fgnu-retain as the option name.

> Wouldn't say -fretain-used-vars be better?


I used -fretain-used-symols for both function and variable.

> > @@ -1666,6 +1666,10 @@ floop-unroll-and-jam

> >  Common Var(flag_unroll_jam) Optimization

> >  Perform unroll-and-jam on loops.

> >

> > +fgnu-retain

> > +Common Var(flag_gnu_retain)

> > +Use SHF_GNU_RETAIN on used symbols if supported by the assembler and the linker.

>

> on variables with the used attribute?


Fixed.

> > diff --git a/gcc/toplev.c b/gcc/toplev.c

> > index d8cc254adef..119cd7c0432 100644

> > --- a/gcc/toplev.c

> > +++ b/gcc/toplev.c

> > @@ -1761,6 +1761,13 @@ process_options (void)

> >    if (flag_large_source_files)

> >      line_table->default_range_bits = 0;

> >

> > +  if (flag_gnu_retain && !SUPPORTS_SHF_GNU_RETAIN)

> > +    {

> > +      warning_at (UNKNOWN_LOCATION, 0, "%qs is not supported for this target",

> > +               "-fgnu-retain");

> > +      flag_gnu_retain = 0;

> > +    }

> > +

> >    /* Please don't change global_options after this point, those changes won't

> >       be reflected in optimization_{default,current}_node.  */

> >  }

> > diff --git a/gcc/varasm.c b/gcc/varasm.c

> > index 29478ab0d8d..4e0e30abee5 100644

> > --- a/gcc/varasm.c

> > +++ b/gcc/varasm.c

> > @@ -297,7 +297,7 @@ get_section (const char *name, unsigned int flags, tree decl,

> >    slot = section_htab->find_slot_with_hash (name, htab_hash_string (name),

> >                                           INSERT);

> >    flags |= SECTION_NAMED;

> > -  if (SUPPORTS_SHF_GNU_RETAIN

> > +  if (flag_gnu_retain

> >        && decl != nullptr

> >        && DECL_P (decl)

> >        && DECL_PRESERVE_P (decl))

> > @@ -487,7 +487,7 @@ resolve_unique_section (tree decl, int reloc ATTRIBUTE_UNUSED,

> >    if (DECL_SECTION_NAME (decl) == NULL

> >        && targetm_common.have_named_sections

> >        && (flag_function_or_data_sections

> > -       || (SUPPORTS_SHF_GNU_RETAIN && DECL_PRESERVE_P (decl))

> > +       || (flag_gnu_retain && DECL_PRESERVE_P (decl))

> >         || DECL_COMDAT_GROUP (decl)))

> >      {

> >        targetm.asm_out.unique_section (decl, reloc);

>

> I'm not convinced this will work properly with LTO, the option from

> -flto -c compilation would be ignored.

> For functions, we usually mark the option Optimization and make it

> saved/restored on function switches, but this is for variables, so it will

> not work for those.

> I'd think better would be to add when seeing "used" attribute some

> artificial attribute (unless we have "retain" attribute to mean that, say

> "retain ") when the flag is on in handle_used_attribute and use that

> attribute in varasm instead of the option?


Fixed.

> > @@ -1227,7 +1227,7 @@ get_variable_section (tree decl, bool prefer_noswitch_p)

> >      vnode->get_constructor ();

> >

> >    if (DECL_COMMON (decl)

> > -      && !(SUPPORTS_SHF_GNU_RETAIN && DECL_PRESERVE_P (decl)))

> > +      && !(flag_gnu_retain && DECL_PRESERVE_P (decl)))

> >      {

> >        /* If the decl has been given an explicit section name, or it resides

> >        in a non-generic address space, then it isn't common, and shouldn't

> > @@ -7761,7 +7761,7 @@ switch_to_section (section *new_section, tree decl)

> >  {

> >    if (in_section == new_section)

> >      {

> > -      if (SUPPORTS_SHF_GNU_RETAIN

> > +      if (flag_gnu_retain

> >         && (new_section->common.flags & SECTION_NAMED)

> >         && decl != nullptr

> >         && DECL_P (decl)

>

> > --- a/gcc/doc/invoke.texi

> > +++ b/gcc/doc/invoke.texi

> > @@ -16168,6 +16168,11 @@ DSOs; if your program relies on reinitialization of a DSO via

> > @code{dlclose} and @code{dlopen}, you can use

> > @option{-fno-gnu-unique}.

> >

> > +@item -fgnu-retain

> > +@opindex fgnu-retain

> > +On systems with recent GNU assembler and linker, the compiler places

> > +used symbols in separate SHF_GNU_RETAIN sections.

>

> Again, variables with the used attribute.


Fixed.

Here is the updated patch to add retain attribute and
-fretain-used-symols.


-- 
H.J.

Comments

Jason Merrill via Gcc-patches Feb. 17, 2021, 6:56 p.m. | #1
On Wed, Feb 17, 2021 at 09:34:34AM -0800, H.J. Lu wrote:
> -fretain-used-symols.

> +/* Add the NAME attribute to *ANODE.  */

> +

> +static void

> +add_attribute (tree *anode, int flags, tree name, tree args, tree ns,

> +	       const bool cxx11_attr_p,

> +	       const struct attribute_spec *spec)


Why do you need this and can't instead do what decl_attributes does
e.g. for "noipa" attribute?

  /* A "noipa" function attribute implies "noinline", "noclone" and "no_icf"
     for those targets that support it.  */
  if (TREE_CODE (*node) == FUNCTION_DECL
      && attributes
      && lookup_attribute ("noipa", attributes) != NULL
      && lookup_attribute_spec (get_identifier ("noipa")))
    {
      if (lookup_attribute ("noinline", attributes) == NULL)
        attributes = tree_cons (get_identifier ("noinline"), NULL, attributes);

      if (lookup_attribute ("noclone", attributes) == NULL)
        attributes = tree_cons (get_identifier ("noclone"),  NULL, attributes);

      if (lookup_attribute ("no_icf", attributes) == NULL)
        attributes = tree_cons (get_identifier ("no_icf"),  NULL, attributes);
    }

Of course, for "used" attributes and flag_retain_used_symbols it would need
to be DECL_P, but otherwise pretty much the same thing.
> --- a/gcc/common.opt

> +++ b/gcc/common.opt

> @@ -2404,6 +2404,10 @@ frerun-loop-opt

>  Common Ignore

>  Does nothing.  Preserved for backward compatibility.

>  

> +fretain-used-symols


s/symols/symbols/ - many times in the patch.

> +@item retain

> +@cindex @code{retain} function attribute

> +This attribute is the same as the @code{used} attribute, except

> +for ELF targets that support the GNU or FreeBSD OSABIs, this attribute


I'm not sure "retain" attribute should imply "used", one can always use
__attribute__((used, retain)) if one wants both.
I think it should only imply the GC avoidance and nothing else.


	Jakub

Patch

From 739912815dcbd4f0683cfcade9ad565d1f913667 Mon Sep 17 00:00:00 2001
From: "H.J. Lu" <hjl.tools@gmail.com>
Date: Mon, 15 Feb 2021 11:31:12 -0800
Subject: [PATCH v3] Add retain attribute to place symbols in SHF_GNU_RETAIN
 section

When building Linux kernel, ld in bninutils 2.36 with GCC 11 generates
thousands of

ld: warning: orphan section `.data.event_initcall_finish' from `init/main.o' being placed in section `.data.event_initcall_finish'
ld: warning: orphan section `.data.event_initcall_start' from `init/main.o' being placed in section `.data.event_initcall_start'
ld: warning: orphan section `.data.event_initcall_level' from `init/main.o' being placed in section `.data.event_initcall_level'

Since these sections are marked with SHF_GNU_RETAIN, they are placed in
separate sections.  They become orphan sections since they aren't expected
in the Linux kernel linker script. But orphan sections normally don't work
well with the Linux kernel linker script and the resulting kernel crashed.

1. Add the "retain" attribute to place symbols in separate SHF_GNU_RETAIN
sections.  Issue a warning if the configured assembler/linker doesn't
support SHF_GNU_RETAIN and treate the "retain" attribute as the "used"
attribute.
2. Add -fretain-used-symols to treat symbols with the "used" attribute
the same as the @code{retain} attribute.

gcc/

	PR target/99113
	* attribs.c (add_attribute): New function.
	(decl_attributes): Add the retain attribute to the used attribute
	for -fretain-used-symols.
	* common.opt: Add -fretain-used-symols.
	* toplev.c (process_options): Issue a warning for
	-fretain-used-symols without SUPPORTS_SHF_GNU_RETAIN.
	* varasm.c (get_section): Replace SUPPORTS_SHF_GNU_RETAIN with
	looking up the retain attribute.
	(resolve_unique_section): Likewise.
	(get_variable_section): Likewise.
	(switch_to_section): Likewise.
	* doc/extend.texi: Document the "retain" attribute.
	* doc/invoke.texi: Document -fretain-used-symols.

gcc/c-family/

	PR target/99113
	* c-attribs.c (c_common_attribute_table): Add the "retain"
	attribute.
	(handle_used_attribute): Update comments.

gcc/testsuite/

	PR target/99113
	* c-c++-common/attr-retain-1.c: New test.
	* c-c++-common/attr-retain-2.c: Likewise.
	* c-c++-common/attr-retain-3.c: Likewise.
	* c-c++-common/attr-retain-4.c: Likewise.
	* c-c++-common/pr99113.c: Likewise.
	* gcc.c-torture/compile/attr-retain-1.c: Likewise.
	* gcc.c-torture/compile/attr-retain-2.c: Likewise.
	* c-c++-common/attr-used.c: Pass -fretain-used-symols if supported.
	* c-c++-common/attr-used-2.c: Likewise.
	* c-c++-common/attr-used-3.c: Likewise.
	* c-c++-common/attr-used-4.c: Likewise.
	* c-c++-common/attr-used-5.c: Likewise.
	* c-c++-common/attr-used-6.c: Likewise.
	* c-c++-common/attr-used-7.c: Likewise.
	* c-c++-common/attr-used-8.c: Likewise.
	* c-c++-common/attr-used-9.c: Likewise.
	* gcc.c-torture/compile/attr-used-retain-1.c: Pass
	-fretain-used-symols.
	* gcc.c-torture/compile/attr-used-retain-2.c: Likewise.
---
 gcc/attribs.c                                 | 153 ++++++++++++------
 gcc/c-family/c-attribs.c                      |   4 +-
 gcc/common.opt                                |   4 +
 gcc/doc/extend.texi                           |  10 +-
 gcc/doc/invoke.texi                           |   6 +
 gcc/testsuite/c-c++-common/attr-retain-1.c    |  14 ++
 gcc/testsuite/c-c++-common/attr-retain-2.c    |  12 ++
 gcc/testsuite/c-c++-common/attr-retain-3.c    |   7 +
 gcc/testsuite/c-c++-common/attr-retain-4.c    |   7 +
 gcc/testsuite/c-c++-common/attr-used-2.c      |   1 +
 gcc/testsuite/c-c++-common/attr-used-3.c      |   1 +
 gcc/testsuite/c-c++-common/attr-used-4.c      |   1 +
 gcc/testsuite/c-c++-common/attr-used-5.c      |   1 +
 gcc/testsuite/c-c++-common/attr-used-6.c      |   1 +
 gcc/testsuite/c-c++-common/attr-used-7.c      |   1 +
 gcc/testsuite/c-c++-common/attr-used-8.c      |   1 +
 gcc/testsuite/c-c++-common/attr-used-9.c      |   1 +
 gcc/testsuite/c-c++-common/attr-used.c        |   1 +
 gcc/testsuite/c-c++-common/pr99113.c          |   7 +
 .../gcc.c-torture/compile/attr-retain-1.c     |  34 ++++
 .../gcc.c-torture/compile/attr-retain-2.c     |  15 ++
 .../compile/attr-used-retain-1.c              |   1 +
 .../compile/attr-used-retain-2.c              |   2 +-
 gcc/toplev.c                                  |   7 +
 gcc/varasm.c                                  |  18 ++-
 25 files changed, 249 insertions(+), 61 deletions(-)
 create mode 100644 gcc/testsuite/c-c++-common/attr-retain-1.c
 create mode 100644 gcc/testsuite/c-c++-common/attr-retain-2.c
 create mode 100644 gcc/testsuite/c-c++-common/attr-retain-3.c
 create mode 100644 gcc/testsuite/c-c++-common/attr-retain-4.c
 create mode 100644 gcc/testsuite/c-c++-common/pr99113.c
 create mode 100644 gcc/testsuite/gcc.c-torture/compile/attr-retain-1.c
 create mode 100644 gcc/testsuite/gcc.c-torture/compile/attr-retain-2.c

diff --git a/gcc/attribs.c b/gcc/attribs.c
index 81322d40f1d..0f3c194ec48 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -459,6 +459,66 @@  diag_attr_exclusions (tree last_decl, tree node, tree attrname,
   return found;
 }
 
+/* Add the NAME attribute to *ANODE.  */
+
+static void
+add_attribute (tree *anode, int flags, tree name, tree args, tree ns,
+	       const bool cxx11_attr_p,
+	       const struct attribute_spec *spec)
+{
+  tree old_attrs;
+  tree a;
+
+  if (DECL_P (*anode))
+    old_attrs = DECL_ATTRIBUTES (*anode);
+  else
+    old_attrs = TYPE_ATTRIBUTES (*anode);
+
+  for (a = lookup_attribute (spec->name, old_attrs);
+       a != NULL_TREE;
+       a = lookup_attribute (spec->name, TREE_CHAIN (a)))
+    {
+      if (simple_cst_equal (TREE_VALUE (a), args) == 1)
+	break;
+    }
+
+  if (a == NULL_TREE)
+    {
+      /* This attribute isn't already in the list.  */
+      tree r;
+      /* Preserve the C++11 form.  */
+      if (cxx11_attr_p)
+	r = tree_cons (build_tree_list (ns, name), args, old_attrs);
+      else
+	r = tree_cons (name, args, old_attrs);
+
+      if (DECL_P (*anode))
+	DECL_ATTRIBUTES (*anode) = r;
+      else if (flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
+	{
+	  TYPE_ATTRIBUTES (*anode) = r;
+	  /* If this is the main variant, also push the attributes
+	     out to the other variants.  */
+	  if (*anode == TYPE_MAIN_VARIANT (*anode))
+	    {
+	      for (tree variant = *anode; variant;
+		   variant = TYPE_NEXT_VARIANT (variant))
+		{
+		  if (TYPE_ATTRIBUTES (variant) == old_attrs)
+		    TYPE_ATTRIBUTES (variant)
+		      = TYPE_ATTRIBUTES (*anode);
+		  else if (!lookup_attribute
+			   (spec->name, TYPE_ATTRIBUTES (variant)))
+		    TYPE_ATTRIBUTES (variant) = tree_cons
+		      (name, args, TYPE_ATTRIBUTES (variant));
+		}
+	    }
+	}
+      else
+	*anode = build_type_attribute_variant (*anode, r);
+    }
+}
+
 /* Process the attributes listed in ATTRIBUTES and install them in *NODE,
    which is either a DECL (including a TYPE_DECL) or a TYPE.  If a DECL,
    it should be modified in place; if a TYPE, a copy should be created
@@ -705,6 +765,8 @@  decl_attributes (tree *node, tree attributes, int flags,
       if (no_add_attrs)
 	continue;
 
+      bool add_retain_attribute_p = false;
+
       if (spec->handler != NULL)
 	{
 	  int cxx11_flag = (cxx11_attr_p ? ATTR_FLAG_CXX11 : 0);
@@ -720,9 +782,43 @@  decl_attributes (tree *node, tree attributes, int flags,
 	  if (anode != node && DECL_P (*node))
 	    cur_and_last_decl[2] = *node;
 
+	  /* Handle "used" and "retain" attributes.  */
+	  int saved_tree_used = 0;
+	  bool retain_attribute_p = is_attribute_p ("retain", name);
+	  if (retain_attribute_p)
+	    {
+	      if (!SUPPORTS_SHF_GNU_RETAIN)
+		{
+		  warning (OPT_Wattributes,
+			   "%qE attribute is treated as %qs attribute",
+			   name, "used");
+		  name = get_identifier ("used");
+		  const struct attribute_spec *spec
+		    = lookup_scoped_attribute_spec (ns, name);
+		}
+	    }
+	  else
+	    {
+	      saved_tree_used = TREE_USED (cur_and_last_decl[0]);
+	      TREE_USED (cur_and_last_decl[0]) = 0;
+	    }
+
 	  tree ret = (spec->handler) (cur_and_last_decl, name, args,
 				      flags|cxx11_flag, &no_add_attrs);
 
+	  if (!retain_attribute_p)
+	    {
+	      if (TREE_USED (cur_and_last_decl[0]))
+		{
+		  /* Add the retain attribute to the used attribute for
+		     -fretain-used-symols.  */
+		  if (flag_retain_used_symbols)
+		    add_retain_attribute_p = true;
+		}
+	      else
+		TREE_USED (cur_and_last_decl[0]) = saved_tree_used;
+	    }
+
 	  *anode = cur_and_last_decl[0];
 	  if (ret == error_mark_node)
 	    {
@@ -742,56 +838,15 @@  decl_attributes (tree *node, tree attributes, int flags,
 
       if (!no_add_attrs)
 	{
-	  tree old_attrs;
-	  tree a;
-
-	  if (DECL_P (*anode))
-	    old_attrs = DECL_ATTRIBUTES (*anode);
-	  else
-	    old_attrs = TYPE_ATTRIBUTES (*anode);
-
-	  for (a = lookup_attribute (spec->name, old_attrs);
-	       a != NULL_TREE;
-	       a = lookup_attribute (spec->name, TREE_CHAIN (a)))
-	    {
-	      if (simple_cst_equal (TREE_VALUE (a), args) == 1)
-		break;
-	    }
-
-	  if (a == NULL_TREE)
+	  add_attribute (anode, flags, name, args, ns, cxx11_attr_p,
+			 spec);
+	  if (add_retain_attribute_p)
 	    {
-	      /* This attribute isn't already in the list.  */
-	      tree r;
-	      /* Preserve the C++11 form.  */
-	      if (cxx11_attr_p)
-		r = tree_cons (build_tree_list (ns, name), args, old_attrs);
-	      else
-		r = tree_cons (name, args, old_attrs);
-
-	      if (DECL_P (*anode))
-		DECL_ATTRIBUTES (*anode) = r;
-	      else if (flags & (int) ATTR_FLAG_TYPE_IN_PLACE)
-		{
-		  TYPE_ATTRIBUTES (*anode) = r;
-		  /* If this is the main variant, also push the attributes
-		     out to the other variants.  */
-		  if (*anode == TYPE_MAIN_VARIANT (*anode))
-		    {
-		      for (tree variant = *anode; variant;
-			   variant = TYPE_NEXT_VARIANT (variant))
-			{
-			  if (TYPE_ATTRIBUTES (variant) == old_attrs)
-			    TYPE_ATTRIBUTES (variant)
-			      = TYPE_ATTRIBUTES (*anode);
-			  else if (!lookup_attribute
-				   (spec->name, TYPE_ATTRIBUTES (variant)))
-			    TYPE_ATTRIBUTES (variant) = tree_cons
-			      (name, args, TYPE_ATTRIBUTES (variant));
-			}
-		    }
-		}
-	      else
-		*anode = build_type_attribute_variant (*anode, r);
+	      name = get_identifier ("retain");
+	      const struct attribute_spec *spec
+		= lookup_scoped_attribute_spec (ns, name);
+	      add_attribute (anode, flags, name, args, ns, cxx11_attr_p,
+			     spec);
 	    }
 	}
 
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index 0cb51fddfaa..8a6db3d072e 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -328,6 +328,8 @@  const struct attribute_spec c_common_attribute_table[] =
 			      handle_used_attribute, NULL },
   { "unused",                 0, 0, false, false, false, false,
 			      handle_unused_attribute, NULL },
+  { "retain",                  0, 0, true,  false, false, false,
+			      handle_used_attribute, NULL },
   { "externally_visible",     0, 0, true,  false, false, false,
 			      handle_externally_visible_attribute, NULL },
   { "no_reorder",	      0, 0, true, false, false, false,
@@ -1500,7 +1502,7 @@  handle_error_attribute (tree *node, tree name, tree args,
   return NULL_TREE;
 }
 
-/* Handle a "used" attribute; arguments as in
+/* Handle "used" and "retain" attributes; arguments as in
    struct attribute_spec.handler.  */
 
 static tree
diff --git a/gcc/common.opt b/gcc/common.opt
index c75dd36843e..d28ca09e039 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -2404,6 +2404,10 @@  frerun-loop-opt
 Common Ignore
 Does nothing.  Preserved for backward compatibility.
 
+fretain-used-symols
+Common Var(flag_retain_used_symbols)
+Treat symbols with the used attribute the same as the retain attribute if supported.
+
 frounding-math
 Common Var(flag_rounding_math) Optimization SetByCombined
 Disable optimizations that assume default FP rounding behavior.
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 6eb1d327e97..4c9fcabaee1 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -3913,7 +3913,10 @@  When applied to a member function of a C++ class template, the
 attribute also means that the function is instantiated if the
 class itself is instantiated.
 
-For ELF targets that support the GNU or FreeBSD OSABIs, this attribute
+@item retain
+@cindex @code{retain} function attribute
+This attribute is the same as the @code{used} attribute, except
+for ELF targets that support the GNU or FreeBSD OSABIs, this attribute
 will also save the function from linker garbage collection.  To support
 this behavior, functions that have not been placed in specific sections
 (e.g. by the @code{section} attribute, or the @code{-ffunction-sections}
@@ -7504,7 +7507,10 @@  When applied to a static data member of a C++ class template, the
 attribute also means that the member is instantiated if the
 class itself is instantiated.
 
-For ELF targets that support the GNU or FreeBSD OSABIs, this attribute
+@item retain
+@cindex @code{retain} variable attribute
+This attribute is the same as the @code{used} attribute, except
+for ELF targets that support the GNU or FreeBSD OSABIs, this attribute
 will also save the variable from linker garbage collection.  To support
 this behavior, variables that have not been placed in specific sections
 (e.g. by the @code{section} attribute, or the @code{-fdata-sections} option),
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index e8baa545eee..6873bc9517b 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -16168,6 +16168,12 @@  DSOs; if your program relies on reinitialization of a DSO via
 @code{dlclose} and @code{dlopen}, you can use
 @option{-fno-gnu-unique}.
 
+@item -fretain-used-symols
+@opindex fretain-used-symols
+On systems with recent GNU assembler and linker, the compiler treats
+symbols with the @code{used} attribute the same as the @code{retain}
+attribute.
+
 @item -fpcc-struct-return
 @opindex fpcc-struct-return
 Return ``short'' @code{struct} and @code{union} values in memory like
diff --git a/gcc/testsuite/c-c++-common/attr-retain-1.c b/gcc/testsuite/c-c++-common/attr-retain-1.c
new file mode 100644
index 00000000000..d091bb58103
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/attr-retain-1.c
@@ -0,0 +1,14 @@ 
+/* { dg-do compile { target R_flag_in_section } } */
+/* { dg-options "-O3" } */
+
+static void function_declaration_before(void) __attribute__((__retain__));
+
+static void function_declaration_before(void) {}
+
+static void function_declaration_after(void) {}
+
+static void function_declaration_after(void) __attribute__((__retain__));
+
+/* { dg-final { scan-assembler "function_declaration_before" } } */
+/* { dg-final { scan-assembler "function_declaration_after" } } */
+/* { dg-final { scan-assembler "\.text.*,\"axR\"" { target R_flag_in_section } } } */
diff --git a/gcc/testsuite/c-c++-common/attr-retain-2.c b/gcc/testsuite/c-c++-common/attr-retain-2.c
new file mode 100644
index 00000000000..65210614259
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/attr-retain-2.c
@@ -0,0 +1,12 @@ 
+/* { dg-do compile { target R_flag_in_section } } */
+/* { dg-options "-Wall -O2" } */
+
+static int xyzzy __attribute__((__retain__)) = 1; 
+
+void foo()
+{
+  int x __attribute__((__retain__)); /* { dg-warning "attribute ignored|unused variable" } */
+}
+
+/* { dg-final { scan-assembler "xyzzy" } } */
+/* { dg-final { scan-assembler "\.data.*,\"awR\"" { target R_flag_in_section } } } */
diff --git a/gcc/testsuite/c-c++-common/attr-retain-3.c b/gcc/testsuite/c-c++-common/attr-retain-3.c
new file mode 100644
index 00000000000..fb6427983dc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/attr-retain-3.c
@@ -0,0 +1,7 @@ 
+/* { dg-do compile { target R_flag_in_section } } */
+/* { dg-options "-Wall -O2 -fcommon" } */
+
+static int xyzzy __attribute__((__retain__)); 
+
+/* { dg-final { scan-assembler "xyzzy" } } */
+/* { dg-final { scan-assembler ",\"awR\"" { target R_flag_in_section } } } */
diff --git a/gcc/testsuite/c-c++-common/attr-retain-4.c b/gcc/testsuite/c-c++-common/attr-retain-4.c
new file mode 100644
index 00000000000..f66111191d7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/attr-retain-4.c
@@ -0,0 +1,7 @@ 
+/* { dg-do compile { target R_flag_in_section } } */
+/* { dg-options "-Wall -O2 -fcommon" } */
+
+int xyzzy __attribute__((__retain__)); 
+
+/* { dg-final { scan-assembler "xyzzy" } } */
+/* { dg-final { scan-assembler ",\"awR\"" { target R_flag_in_section } } } */
diff --git a/gcc/testsuite/c-c++-common/attr-used-2.c b/gcc/testsuite/c-c++-common/attr-used-2.c
index eef2519643f..49a853c4cce 100644
--- a/gcc/testsuite/c-c++-common/attr-used-2.c
+++ b/gcc/testsuite/c-c++-common/attr-used-2.c
@@ -1,5 +1,6 @@ 
 /* { dg-do compile } */
 /* { dg-options "-Wall -O2" } */
+/* { dg-additional-options "-fretain-used-symols" { target R_flag_in_section } } */
 
 static int xyzzy __attribute__((__used__)) = 1; 
 
diff --git a/gcc/testsuite/c-c++-common/attr-used-3.c b/gcc/testsuite/c-c++-common/attr-used-3.c
index ca64197929c..98f8f9a17af 100644
--- a/gcc/testsuite/c-c++-common/attr-used-3.c
+++ b/gcc/testsuite/c-c++-common/attr-used-3.c
@@ -1,5 +1,6 @@ 
 /* { dg-do compile } */
 /* { dg-options "-Wall -O2 -fcommon" } */
+/* { dg-additional-options "-fretain-used-symols" { target R_flag_in_section } } */
 
 static int xyzzy __attribute__((__used__)); 
 
diff --git a/gcc/testsuite/c-c++-common/attr-used-4.c b/gcc/testsuite/c-c++-common/attr-used-4.c
index 1cbc4c703e9..76de08266b3 100644
--- a/gcc/testsuite/c-c++-common/attr-used-4.c
+++ b/gcc/testsuite/c-c++-common/attr-used-4.c
@@ -1,5 +1,6 @@ 
 /* { dg-do compile } */
 /* { dg-options "-Wall -O2 -fcommon" } */
+/* { dg-additional-options "-fretain-used-symols" { target R_flag_in_section } } */
 
 int xyzzy __attribute__((__used__)); 
 
diff --git a/gcc/testsuite/c-c++-common/attr-used-5.c b/gcc/testsuite/c-c++-common/attr-used-5.c
index 5b4924160fd..99f3b236cd7 100644
--- a/gcc/testsuite/c-c++-common/attr-used-5.c
+++ b/gcc/testsuite/c-c++-common/attr-used-5.c
@@ -1,6 +1,7 @@ 
 /* { dg-do compile } */
 /* { dg-skip-if "non-ELF target" { *-*-darwin* } } */
 /* { dg-options "-Wall -O2" } */
+/* { dg-additional-options "-fretain-used-symols" { target R_flag_in_section } } */
 
 struct dtv_slotinfo_list
 {
diff --git a/gcc/testsuite/c-c++-common/attr-used-6.c b/gcc/testsuite/c-c++-common/attr-used-6.c
index 3cf288dd28f..575f8cac8fb 100644
--- a/gcc/testsuite/c-c++-common/attr-used-6.c
+++ b/gcc/testsuite/c-c++-common/attr-used-6.c
@@ -1,6 +1,7 @@ 
 /* { dg-do compile } */
 /* { dg-skip-if "non-ELF target" { *-*-darwin* } } */
 /* { dg-options "-Wall -O2" } */
+/* { dg-additional-options "-fretain-used-symols" { target R_flag_in_section } } */
 
 struct dtv_slotinfo_list
 {
diff --git a/gcc/testsuite/c-c++-common/attr-used-7.c b/gcc/testsuite/c-c++-common/attr-used-7.c
index 1721a8afc4e..0aea2016622 100644
--- a/gcc/testsuite/c-c++-common/attr-used-7.c
+++ b/gcc/testsuite/c-c++-common/attr-used-7.c
@@ -1,6 +1,7 @@ 
 /* { dg-do compile } */
 /* { dg-skip-if "non-ELF target" { *-*-darwin* } } */
 /* { dg-options "-Wall -O2" } */
+/* { dg-additional-options "-fretain-used-symols" { target R_flag_in_section } } */
 
 int __attribute__((used,section(".data.foo"))) foo2 = 2;
 int __attribute__((section(".data.foo"))) foo1 = 1;
diff --git a/gcc/testsuite/c-c++-common/attr-used-8.c b/gcc/testsuite/c-c++-common/attr-used-8.c
index 20662cadf70..cf669701ae6 100644
--- a/gcc/testsuite/c-c++-common/attr-used-8.c
+++ b/gcc/testsuite/c-c++-common/attr-used-8.c
@@ -1,6 +1,7 @@ 
 /* { dg-do compile } */
 /* { dg-skip-if "non-ELF target" { *-*-darwin* } } */
 /* { dg-options "-Wall -O2" } */
+/* { dg-additional-options "-fretain-used-symols" { target R_flag_in_section } } */
 
 int __attribute__((section(".data.foo"))) foo1 = 1;
 /* { dg-warning "'.*' without 'used' attribute and '.*' with 'used' attribute are placed in a section with the same name" "" { target R_flag_in_section } .-1 } */
diff --git a/gcc/testsuite/c-c++-common/attr-used-9.c b/gcc/testsuite/c-c++-common/attr-used-9.c
index 5847b0550ce..75da5f8f6c7 100644
--- a/gcc/testsuite/c-c++-common/attr-used-9.c
+++ b/gcc/testsuite/c-c++-common/attr-used-9.c
@@ -1,6 +1,7 @@ 
 /* { dg-do compile } */
 /* { dg-skip-if "non-ELF target" { *-*-darwin* } } */
 /* { dg-options "-Wall -O2" } */
+/* { dg-additional-options "-fretain-used-symols" { target R_flag_in_section } } */
 
 struct dtv_slotinfo_list
 {
diff --git a/gcc/testsuite/c-c++-common/attr-used.c b/gcc/testsuite/c-c++-common/attr-used.c
index 2036533c959..b6b7c48de39 100644
--- a/gcc/testsuite/c-c++-common/attr-used.c
+++ b/gcc/testsuite/c-c++-common/attr-used.c
@@ -1,5 +1,6 @@ 
 /* { dg-do compile } */
 /* { dg-options "-O3" } */
+/* { dg-additional-options "-fretain-used-symols" { target R_flag_in_section } } */
 
 static void function_declaration_before(void) __attribute__((__used__));
 
diff --git a/gcc/testsuite/c-c++-common/pr99113.c b/gcc/testsuite/c-c++-common/pr99113.c
new file mode 100644
index 00000000000..01814014ac8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/pr99113.c
@@ -0,0 +1,7 @@ 
+/* { dg-do compile } */
+/* { dg-options "-Wall -O2" } */
+
+static int xyzzy __attribute__((__used__)) = 1; 
+
+/* { dg-final { scan-assembler "xyzzy" } } */
+/* { dg-final { scan-assembler-not "\.data.*,\"awR\"" { target R_flag_in_section } } } */
diff --git a/gcc/testsuite/gcc.c-torture/compile/attr-retain-1.c b/gcc/testsuite/gcc.c-torture/compile/attr-retain-1.c
new file mode 100644
index 00000000000..f36df2f16bd
--- /dev/null
+++ b/gcc/testsuite/gcc.c-torture/compile/attr-retain-1.c
@@ -0,0 +1,34 @@ 
+/* { dg-do compile { target R_flag_in_section } } */
+/* { dg-final { scan-assembler ".text.*,\"axR\"" } } */
+/* { dg-final { scan-assembler ".bss.*,\"awR\"" } } */
+/* { dg-final { scan-assembler ".data.*,\"awR\"" } } */
+/* { dg-final { scan-assembler ".rodata.*,\"aR\"" } } */
+/* { dg-final { scan-assembler ".data.used_foo_sec,\"awR\"" } } */
+
+void __attribute__((retain)) used_fn (void) { }
+void unused_fn (void) { }
+void __attribute__((hot,retain)) used_hot_fn (void) { }
+void __attribute__((hot)) unused_hot_fn (void) { }
+void __attribute__((cold,retain)) used_cold_fn (void) { }
+void __attribute__((cold)) unused_cold_fn (void) { }
+int __attribute__((retain)) used_bss = 0;
+int __attribute__((retain)) used_data = 1;
+const int __attribute__((retain)) used_rodata = 2;
+int __attribute__((retain)) used_comm;
+static int __attribute__((retain)) used_lcomm;
+
+int unused_bss = 0;
+int unused_data = 1;
+const int unused_rodata = 2;
+int unused_comm;
+static int unused_lcomm;
+
+/* Test switching back to the retained sections.  */
+void __attribute__((retain)) used_fn2 (void) { }
+int __attribute__((retain)) used_bss2 = 0;
+int __attribute__((retain)) used_data2 = 1;
+const int __attribute__((retain)) used_rodata2 = 2;
+int __attribute__((retain)) used_comm2;
+static int __attribute__((retain)) used_lcomm2;
+
+int __attribute__((retain,section(".data.used_foo_sec"))) used_foo = 2;
diff --git a/gcc/testsuite/gcc.c-torture/compile/attr-retain-2.c b/gcc/testsuite/gcc.c-torture/compile/attr-retain-2.c
new file mode 100644
index 00000000000..0208ffe37ab
--- /dev/null
+++ b/gcc/testsuite/gcc.c-torture/compile/attr-retain-2.c
@@ -0,0 +1,15 @@ 
+/* { dg-do compile { target R_flag_in_section } } */
+/* { dg-final { scan-assembler ".text.used_fn,\"axR\"" } } */
+/* { dg-final { scan-assembler ".text.used_fn2,\"axR\"" } } */
+/* { dg-final { scan-assembler ".bss.used_bss,\"awR\"" } } */
+/* { dg-final { scan-assembler ".bss.used_bss2,\"awR\"" } } */
+/* { dg-final { scan-assembler ".data.used_data,\"awR\"" } } */
+/* { dg-final { scan-assembler ".data.used_data2,\"awR\"" } } */
+/* { dg-final { scan-assembler ".rodata.used_rodata,\"aR\"" } } */
+/* { dg-final { scan-assembler ".rodata.used_rodata2,\"aR\"" } } */
+/* { dg-final { scan-assembler ".bss.used_lcomm,\"awR\"" { target arm-*-* } } } */
+/* { dg-final { scan-assembler ".bss.used_lcomm2,\"awR\"" { target arm-*-* } } } */
+/* { dg-final { scan-assembler ".data.used_foo_sec,\"awR\"" } } */
+/* { dg-options "-ffunction-sections -fdata-sections" } */
+
+#include "attr-retain-1.c"
diff --git a/gcc/testsuite/gcc.c-torture/compile/attr-used-retain-1.c b/gcc/testsuite/gcc.c-torture/compile/attr-used-retain-1.c
index 5f6cbca6e33..69a558d6094 100644
--- a/gcc/testsuite/gcc.c-torture/compile/attr-used-retain-1.c
+++ b/gcc/testsuite/gcc.c-torture/compile/attr-used-retain-1.c
@@ -5,6 +5,7 @@ 
 /* { dg-final { scan-assembler ".data.*,\"awR\"" } } */
 /* { dg-final { scan-assembler ".rodata.*,\"aR\"" } } */
 /* { dg-final { scan-assembler ".data.used_foo_sec,\"awR\"" } } */
+/* { dg-options "-fretain-used-symols" } */
 
 void __attribute__((used)) used_fn (void) { }
 void unused_fn (void) { }
diff --git a/gcc/testsuite/gcc.c-torture/compile/attr-used-retain-2.c b/gcc/testsuite/gcc.c-torture/compile/attr-used-retain-2.c
index be5f3917ac8..6c284df7234 100644
--- a/gcc/testsuite/gcc.c-torture/compile/attr-used-retain-2.c
+++ b/gcc/testsuite/gcc.c-torture/compile/attr-used-retain-2.c
@@ -11,6 +11,6 @@ 
 /* { dg-final { scan-assembler ".bss.used_lcomm,\"awR\"" { target arm-*-* } } } */
 /* { dg-final { scan-assembler ".bss.used_lcomm2,\"awR\"" { target arm-*-* } } } */
 /* { dg-final { scan-assembler ".data.used_foo_sec,\"awR\"" } } */
-/* { dg-options "-ffunction-sections -fdata-sections" } */
+/* { dg-options "-ffunction-sections -fdata-sections -fretain-used-symols" } */
 
 #include "attr-used-retain-1.c"
diff --git a/gcc/toplev.c b/gcc/toplev.c
index d8cc254adef..4b5a65f554f 100644
--- a/gcc/toplev.c
+++ b/gcc/toplev.c
@@ -1761,6 +1761,13 @@  process_options (void)
   if (flag_large_source_files)
     line_table->default_range_bits = 0;
 
+  if (flag_retain_used_symbols && !SUPPORTS_SHF_GNU_RETAIN)
+    {
+      warning_at (UNKNOWN_LOCATION, 0, "%qs is not supported for this target",
+		  "-fretain-used-symols");
+      flag_retain_used_symbols = 0;
+    }
+
   /* Please don't change global_options after this point, those changes won't
      be reflected in optimization_{default,current}_node.  */
 }
diff --git a/gcc/varasm.c b/gcc/varasm.c
index 29478ab0d8d..e85a0d5a51a 100644
--- a/gcc/varasm.c
+++ b/gcc/varasm.c
@@ -297,10 +297,10 @@  get_section (const char *name, unsigned int flags, tree decl,
   slot = section_htab->find_slot_with_hash (name, htab_hash_string (name),
 					    INSERT);
   flags |= SECTION_NAMED;
-  if (SUPPORTS_SHF_GNU_RETAIN
-      && decl != nullptr
+  if (decl != nullptr
       && DECL_P (decl)
-      && DECL_PRESERVE_P (decl))
+      && DECL_PRESERVE_P (decl)
+      && lookup_attribute ("retain", DECL_ATTRIBUTES (decl)))
     flags |= SECTION_RETAIN;
   if (*slot == NULL)
     {
@@ -487,7 +487,8 @@  resolve_unique_section (tree decl, int reloc ATTRIBUTE_UNUSED,
   if (DECL_SECTION_NAME (decl) == NULL
       && targetm_common.have_named_sections
       && (flag_function_or_data_sections
-	  || (SUPPORTS_SHF_GNU_RETAIN && DECL_PRESERVE_P (decl))
+	  || (DECL_PRESERVE_P (decl)
+	      && lookup_attribute ("retain", DECL_ATTRIBUTES (decl)))
 	  || DECL_COMDAT_GROUP (decl)))
     {
       targetm.asm_out.unique_section (decl, reloc);
@@ -1227,7 +1228,8 @@  get_variable_section (tree decl, bool prefer_noswitch_p)
     vnode->get_constructor ();
 
   if (DECL_COMMON (decl)
-      && !(SUPPORTS_SHF_GNU_RETAIN && DECL_PRESERVE_P (decl)))
+      && !(DECL_PRESERVE_P (decl)
+	   && lookup_attribute ("retain", DECL_ATTRIBUTES (decl))))
     {
       /* If the decl has been given an explicit section name, or it resides
 	 in a non-generic address space, then it isn't common, and shouldn't
@@ -7761,12 +7763,12 @@  switch_to_section (section *new_section, tree decl)
 {
   if (in_section == new_section)
     {
-      if (SUPPORTS_SHF_GNU_RETAIN
-	  && (new_section->common.flags & SECTION_NAMED)
+      if ((new_section->common.flags & SECTION_NAMED)
 	  && decl != nullptr
 	  && DECL_P (decl)
 	  && (!!DECL_PRESERVE_P (decl)
-	      != !!(new_section->common.flags & SECTION_RETAIN)))
+	      != !!(new_section->common.flags & SECTION_RETAIN))
+	  && lookup_attribute ("retain", DECL_ATTRIBUTES (decl)))
 	{
 	  /* If the SECTION_RETAIN bit doesn't match, switch to a new
 	     section.  */
-- 
2.29.2