Hi, The attached patch adds support for the new "retain" attribute, which can be applied to function and variable declarations, to protect them from linker garbage collection.
Declarations with this attribute will be placed in a new, unique section, and the ".section" assembler directive that gets output for that new section will have the "R" flag applied. The "R" flag indicates to GAS that the SHF_GNU_RETAIN flag should be applied to the section. SHF_GNU_RETAIN is a GNU OSABI ELF extension. SHF_GNU_RETAIN was discussed on the GNU gABI mailing list here: https://sourceware.org/pipermail/gnu-gabi/2020q3/000429.html The Binutils patch for SHF_GNU_RETAIN was discussed in the following threads: https://sourceware.org/pipermail/binutils/2020-September/113406.html https://sourceware.org/pipermail/binutils/2020-September/113499.html The latest Binutils patch is posted here: https://sourceware.org/pipermail/binutils/2020-October/113769.html It is awaiting final approval, but in the meantime I'm posting the GCC part of the implementation for review. Successfully bootstrapped and regtested for x86_64-pc-linux-gnu, and regtested for arm-none-eabi. Ok for trunk? Thanks, Jozef
>From 82c4f86cde2155dd8b89ba01a2da88761586787b Mon Sep 17 00:00:00 2001 From: Jozef Lawrynowicz <joze...@mittosystems.com> Date: Thu, 22 Oct 2020 14:23:40 +0100 Subject: [PATCH] Implement the "retain" attribute The "retain" attribute is used to protect the function or variable declaration it is applied to from linker garbage collection. Declarations with this attribute will be placed in a new, unique section, and the ".section" assembler directive that gets output for that new section will have the "R" flag applied. The "R" flag indicates to GAS that the SHF_GNU_RETAIN flag should be applied to the section. SHF_GNU_RETAIN is a GNU OSABI ELF extension. gcc/c-family/ChangeLog: * c-attribs.c (handle_retain_attribute): New. gcc/ChangeLog: * config.in: Regenerate. * configure: Regenerate. * configure.ac: Define HAVE_GAS_SECTION_RETAIN if the assembler .section directive supports the "R" flag. * doc/extend.texi: Document the "retain" function and variable attribute. * doc/sourcebuild.texi: Document the "retain" DejaGNU effective target keyword. * output.h (SECTION_RETAIN): Define. (SECTION_MACH_DEP): Adjust value. * tree-core.h (struct tree_decl_common): Add "retain_flag" member. * tree.h (DECL_RETAIN_P): New. * varasm.c (resolve_unique_section): Create unique section for DECL_RETAIN_P decls. (default_section_type_flags): Set the SECTION_RETAIN flag for sections created for DECL_RETAIN_P decls. (default_elf_asm_named_section): Emit "R" flag in .section directive for SECTION_RETAIN sections. gcc/testsuite/ChangeLog: * lib/target-supports.exp (check_effective_target_retain): New. * c-c++-common/attr-retain-1.c: New test. * c-c++-common/attr-retain-2.c: New test. * c-c++-common/attr-retain-3.c: New test. * c-c++-common/attr-retain-main.inc: New test. --- gcc/c-family/c-attribs.c | 42 +++++++++++++++++++ gcc/config.in | 6 +++ gcc/configure | 36 ++++++++++++++++ gcc/configure.ac | 9 ++++ gcc/doc/extend.texi | 23 ++++++++++ gcc/doc/sourcebuild.texi | 3 ++ gcc/output.h | 3 +- gcc/testsuite/c-c++-common/attr-retain-1.c | 19 +++++++++ gcc/testsuite/c-c++-common/attr-retain-2.c | 21 ++++++++++ gcc/testsuite/c-c++-common/attr-retain-3.c | 10 +++++ .../c-c++-common/attr-retain-main.inc | 26 ++++++++++++ gcc/testsuite/lib/target-supports.exp | 9 ++++ gcc/tree-core.h | 1 + gcc/tree.h | 7 ++++ gcc/varasm.c | 8 +++- 15 files changed, 221 insertions(+), 2 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-main.inc diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c index a3b2b3d58bd..72c0c61993e 100644 --- a/gcc/c-family/c-attribs.c +++ b/gcc/c-family/c-attribs.c @@ -155,6 +155,7 @@ static tree handle_designated_init_attribute (tree *, tree, tree, int, bool *); static tree handle_patchable_function_entry_attribute (tree *, tree, tree, int, bool *); static tree handle_copy_attribute (tree *, tree, tree, int, bool *); +static tree handle_retain_attribute (tree *, tree, tree, int, bool *); /* Helper to define attribute exclusions. */ #define ATTR_EXCL(name, function, type, variable) \ @@ -505,6 +506,8 @@ const struct attribute_spec c_common_attribute_table[] = handle_noinit_attribute, attr_noinit_exclusions }, { "access", 1, 3, false, true, true, false, handle_access_attribute, NULL }, + { "retain", 0, 0, true, false, false, false, + handle_retain_attribute, NULL }, { NULL, 0, 0, false, false, false, false, NULL, NULL } }; @@ -2592,6 +2595,45 @@ handle_alias_ifunc_attribute (bool is_alias, tree *node, tree name, tree args, decl, is_alias ? "alias" : "ifunc"); } + return NULL_TREE; +} + +/* Handle a "retain" attribute; arguments as in + struct attribute_spec.handler. */ +static tree +handle_retain_attribute (tree *pnode ATTRIBUTE_UNUSED, + tree name, + tree args ATTRIBUTE_UNUSED, + int flags ATTRIBUTE_UNUSED, + bool *no_add_attrs) +{ +#if HAVE_GAS_SECTION_RETAIN + tree node = *pnode; + + if (!targetm_common.have_named_sections) + { + warning (OPT_Wattributes, "%qE attribute not supported by this target", + name); + *no_add_attrs = true; + } + else if (TREE_CODE (node) == FUNCTION_DECL + || (VAR_P (node) && TREE_STATIC (node))) + { + TREE_USED (node) = 1; + DECL_PRESERVE_P (node) = 1; + DECL_RETAIN_P (node) = 1; + if (VAR_P (node)) + DECL_READ_P (node) = 1; + } + else + { + warning (OPT_Wattributes, "%qE attribute ignored", name); + *no_add_attrs = true; + } +#else + warning (OPT_Wattributes, "%qE attribute not supported by this target", name); + *no_add_attrs = true; +#endif return NULL_TREE; } diff --git a/gcc/config.in b/gcc/config.in index 3657c46f349..343f2929588 100644 --- a/gcc/config.in +++ b/gcc/config.in @@ -1352,6 +1352,12 @@ #endif +/* Define if your assembler supports specifying the retain section flag. */ +#ifndef USED_FOR_TARGET +#undef HAVE_GAS_SECTION_RETAIN +#endif + + /* Define 0/1 if your assembler supports marking sections with SHF_MERGE flag. */ #ifndef USED_FOR_TARGET diff --git a/gcc/configure b/gcc/configure index abff47d30eb..7abda5c13ab 100755 --- a/gcc/configure +++ b/gcc/configure @@ -24223,6 +24223,42 @@ cat >>confdefs.h <<_ACEOF _ACEOF +# Test if the assembler supports the section flag 'R' for specifying a +# section which should not be garbage collected by the linker. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking assembler for section retain flag" >&5 +$as_echo_n "checking assembler for section retain flag... " >&6; } +if ${gcc_cv_as_section_retain_r+:} false; then : + $as_echo_n "(cached) " >&6 +else + gcc_cv_as_section_retain_r=no + if test x$gcc_cv_as != x; then + $as_echo '.section foo1,"aR" + .byte 0' > conftest.s + if { ac_try='$gcc_cv_as $gcc_cv_as_flags -o conftest.o conftest.s >&5' + { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5 + (eval $ac_try) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; } + then + gcc_cv_as_section_retain_r=yes + else + echo "configure: failed program was" >&5 + cat conftest.s >&5 + fi + rm -f conftest.o conftest.s + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $gcc_cv_as_section_retain_r" >&5 +$as_echo "$gcc_cv_as_section_retain_r" >&6; } + + + +cat >>confdefs.h <<_ACEOF +#define HAVE_GAS_SECTION_RETAIN `if test $gcc_cv_as_section_retain_r = yes; then echo 1; else echo 0; fi` +_ACEOF + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking assembler for section merging support" >&5 $as_echo_n "checking assembler for section merging support... " >&6; } if ${gcc_cv_as_shf_merge+:} false; then : diff --git a/gcc/configure.ac b/gcc/configure.ac index 26a5d8e3619..035df137cf6 100644 --- a/gcc/configure.ac +++ b/gcc/configure.ac @@ -3216,6 +3216,15 @@ AC_DEFINE_UNQUOTED(HAVE_GAS_SECTION_EXCLUDE, [`if test $gcc_cv_as_section_exclude_e = yes || test $gcc_cv_as_section_exclude_hash = yes; then echo 1; else echo 0; fi`], [Define if your assembler supports specifying the exclude section flag.]) +# Test if the assembler supports the section flag 'R' for specifying a +# section which should not be garbage collected by the linker. +gcc_GAS_CHECK_FEATURE([section retain flag], gcc_cv_as_section_retain_r,,, + [.section foo1,"aR" + .byte 0]) +AC_DEFINE_UNQUOTED(HAVE_GAS_SECTION_RETAIN, + [`if test $gcc_cv_as_section_retain_r = yes; then echo 1; else echo 0; fi`], +[Define if your assembler supports specifying the retain section flag.]) + gcc_GAS_CHECK_FEATURE(section merging support, gcc_cv_as_shf_merge, [elf,2,12,0], [--fatal-warnings], [.section .rodata.str, "aMS", @progbits, 1]) diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index 4a7c85822a7..25d49382fbb 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -3578,6 +3578,17 @@ diagnosed. Because a pure function cannot have any observable side effects it does not make sense for such a function to return @code{void}. Declaring such a function is diagnosed. +@item retain +@cindex @code{retain} function attribute +The @code{retain} attribute, attached to a function, means that function must +not be garbage collected by the linker, even if it appears unused. + +The function is put in a new, unique section marked with the +@code{SHF_GNU_RETAIN} flag. This flag is a GNU extension to the ELF format. + +This attribute implies, and has the same restrictions as, the @code{used} +attribute. + @item returns_nonnull @cindex @code{returns_nonnull} function attribute The @code{returns_nonnull} attribute specifies that the function @@ -7197,6 +7208,18 @@ been fixed in GCC 4.4 but the change can lead to differences in the structure layout. See the documentation of @option{-Wpacked-bitfield-compat} for more information. +@item retain +@cindex @code{retain} variable attribute +The @code{retain} attribute, attached to a variable with static storage, means +that variable must not be garbage collected by the linker, even if it appears +unused. + +The variable is put in a new, unique section marked with the +@code{SHF_GNU_RETAIN} flag. This flag is a GNU extension to the ELF format. + +This attribute implies, and has the same restrictions as, the @code{used} +attribute. + @item section ("@var{section-name}") @cindex @code{section} variable attribute Normally, the compiler places the objects it generates in sections like diff --git a/gcc/doc/sourcebuild.texi b/gcc/doc/sourcebuild.texi index 49316a5d0ff..85f13483d40 100644 --- a/gcc/doc/sourcebuild.texi +++ b/gcc/doc/sourcebuild.texi @@ -2551,6 +2551,9 @@ Target supports @option{-pie}, @option{-fpie} and @option{-fPIE}. @item rdynamic Target supports @option{-rdynamic}. +@item retain +Target supports the @code{retain} function/variable attribute. + @item scalar_all_fma Target supports all four fused multiply-add optabs for both @code{float} and @code{double}. These optabs are: @code{fma_optab}, @code{fms_optab}, diff --git a/gcc/output.h b/gcc/output.h index eb253c50329..2a723969bd9 100644 --- a/gcc/output.h +++ b/gcc/output.h @@ -381,7 +381,8 @@ extern void no_asm_to_stream (FILE *); #define SECTION_COMMON 0x800000 /* contains common data */ #define SECTION_RELRO 0x1000000 /* data is readonly after relocation processing */ #define SECTION_EXCLUDE 0x2000000 /* discarded by the linker */ -#define SECTION_MACH_DEP 0x4000000 /* subsequent bits reserved for target */ +#define SECTION_RETAIN 0x4000000 /* retained by the linker, SHF_GNU_RETAIN */ +#define SECTION_MACH_DEP 0x8000000 /* subsequent bits reserved for target */ /* This SECTION_STYLE is used for unnamed sections that we can switch to using a special assembler directive. */ 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..c264eb654fc --- /dev/null +++ b/gcc/testsuite/c-c++-common/attr-retain-1.c @@ -0,0 +1,19 @@ +/* { dg-do compile } */ +/* { dg-require-effective-target retain } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain0\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain1\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain2\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain3\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*sretain0\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*sretain1\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*sretain3\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*fretain0\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*retained_var\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*retained_fn\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain4\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain5\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain6\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain7\[^,\]*,\"\\w+R" } } */ + +/* Test the retain attribute. */ +#include "attr-retain-main.inc" 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..a69516547ae --- /dev/null +++ b/gcc/testsuite/c-c++-common/attr-retain-2.c @@ -0,0 +1,21 @@ +/* { dg-do compile } */ +/* { dg-require-effective-target retain } */ +/* { dg-options "-ffunction-sections -fdata-sections" } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain0\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain1\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain2\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain3\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*sretain0\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*sretain1\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*sretain3\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*fretain0\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*retained_var\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*retained_fn\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain4\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain5\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain6\[^,\]*,\"\\w+R" } } */ +/* { dg-final { scan-assembler "\\.section\t\[^,\]*retain7\[^,\]*,\"\\w+R" } } */ + +/* Test the retain attribute when -ffunction-sections and -fdata-sections + options are in use. */ +#include "attr-retain-main.inc" 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..a9f2b20183c --- /dev/null +++ b/gcc/testsuite/c-c++-common/attr-retain-3.c @@ -0,0 +1,10 @@ +/* { dg-do compile } */ +/* { dg-require-effective-target retain } */ + +typedef int int_retain __attribute__((retain)); /* { dg-warning "'retain' attribute ignored" } */ + +void +foo (void) +{ + int __attribute__((retain)) a; /* { dg-warning "'retain' attribute ignored" } */ +} diff --git a/gcc/testsuite/c-c++-common/attr-retain-main.inc b/gcc/testsuite/c-c++-common/attr-retain-main.inc new file mode 100644 index 00000000000..0ae3b71b9ce --- /dev/null +++ b/gcc/testsuite/c-c++-common/attr-retain-main.inc @@ -0,0 +1,26 @@ +/* The "retain" attribute set on this group of functions and data should protect + them from linker garbage collection. */ +int __attribute__((retain)) retain0; +int __attribute__((retain)) retain1 = 0; +int __attribute__((retain)) retain2 = 1; +const int __attribute__((retain)) retain3 = 2; +static int __attribute__((retain)) sretain0; +static int __attribute__((retain)) sretain1 = 0; +static int __attribute__((retain)) sretain2 = 1; +static const int __attribute__((retain)) sretain3 = 2; + +void __attribute__((retain)) fretain0 (void) {} + +int __attribute__((retain,section(".data.retained_var"))) var_retain_sec = 42; +void __attribute__((retain,section(".text.retained_fn"))) fn_retain_sec (void) {} + +/* The section containing fdiscard2 should be garbage collected, but the static + variables defined inside it with the retain attribute applied should not. */ +void +fdiscard2 (void) +{ + static int __attribute__((retain)) retain4; + static int __attribute__((retain)) retain5 = 0; + static int __attribute__((retain)) retain6 = 1; + static const int __attribute__((retain)) retain7 = 2; +} diff --git a/gcc/testsuite/lib/target-supports.exp b/gcc/testsuite/lib/target-supports.exp index 8439720baea..af38d7e1155 100644 --- a/gcc/testsuite/lib/target-supports.exp +++ b/gcc/testsuite/lib/target-supports.exp @@ -380,6 +380,15 @@ proc check_effective_target_noinit { } { return 0 } +# The retain attribute is only supported by some targets. +# This proc returns 1 if it's supported, 0 if it's not. + +proc check_effective_target_retain { } { + return [check_no_compiler_messages retain_available assembly { + int __attribute__((retain)) a = 1; + }] +} + ############################### # proc check_visibility_available { what_kind } ############################### diff --git a/gcc/tree-core.h b/gcc/tree-core.h index 752bec31c3f..42e6e4b82f5 100644 --- a/gcc/tree-core.h +++ b/gcc/tree-core.h @@ -1691,6 +1691,7 @@ struct GTY(()) tree_decl_common { unsigned abstract_flag : 1; unsigned artificial_flag : 1; unsigned preserve_flag: 1; + unsigned retain_flag: 1; unsigned debug_expr_is_from : 1; unsigned lang_flag_0 : 1; diff --git a/gcc/tree.h b/gcc/tree.h index f43ac9f1942..731d6d8c558 100644 --- a/gcc/tree.h +++ b/gcc/tree.h @@ -2648,6 +2648,13 @@ extern tree vector_element_bits_tree (const_tree); #define DECL_PRESERVE_P(DECL) \ DECL_COMMON_CHECK (DECL)->decl_common.preserve_flag +/* Nonzero for a decl that is decorated using attribute retain. + This indicates to compiler tools that this decl needs to be preserved, + and to the link editor that the section containing the decl should not be + garbage collected. */ +#define DECL_RETAIN_P(DECL) \ + DECL_COMMON_CHECK (DECL)->decl_common.retain_flag + /* For function local variables of COMPLEX and VECTOR types, indicates that the variable is not aliased, and that all modifications to the variable have been adjusted so that diff --git a/gcc/varasm.c b/gcc/varasm.c index ea0b59cf44a..dc7c7176a10 100644 --- a/gcc/varasm.c +++ b/gcc/varasm.c @@ -464,7 +464,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 - || DECL_COMDAT_GROUP (decl))) + || DECL_COMDAT_GROUP (decl) + || DECL_RETAIN_P (decl))) { targetm.asm_out.unique_section (decl, reloc); if (DECL_SECTION_NAME (decl)) @@ -6619,6 +6620,9 @@ default_section_type_flags (tree decl, const char *name, int reloc) if (decl && VAR_P (decl) && DECL_THREAD_LOCAL_P (decl)) flags |= SECTION_TLS | SECTION_WRITE; + if (decl && DECL_RETAIN_P (decl)) + flags |= SECTION_RETAIN; + if (strcmp (name, ".bss") == 0 || strncmp (name, ".bss.", 5) == 0 || strncmp (name, ".gnu.linkonce.b.", 16) == 0 @@ -6738,6 +6742,8 @@ default_elf_asm_named_section (const char *name, unsigned int flags, if (flags & SECTION_MACH_DEP) *f++ = MACH_DEP_SECTION_ASM_FLAG; #endif + if (flags & SECTION_RETAIN) + *f++ = 'R'; *f = '\0'; } -- 2.28.0