Hi H.J.,
On Thu, Dec 03, 2020 at 04:06:51PM -0800, H.J. Lu via Gcc-patches wrote:
> When definitions marked with used attribute and unmarked definitions are
> placed in the same section, switch to a new section if the SECTION_RETAIN
> bit doesn't match.
GAS doesn't create separate sections for
.section .data.foo,"aw"
.section .data.foo,"awR"
A single .data.foo section, with SHF_GNU_RETAIN set, is output to the
object file.
This is because the addition of SHF_GNU_RETAIN support to the "used"
attribute was not supposed to be modifying the layout of sections in the
object file.
Now we are placing "used" decls in a new, unique section (when they
don't already have a unique section), I suppose it is fine to have two
separate sections for retained/non-retained .data.foo in the object
file.
It does feel like a bit of a kludge that we end up with
[ 4] .data.foo PROGBITS 0000000000000000 000040 000006 00 WA 0 0 1
[ 5] .data.foo PROGBITS 0000000000000000 000046 000006 00 WAR 0 0 1
but it is beneficial to the user, since linker garbage collection
will remove more unused parts of their program.
Your commit "2be7ea19b3 Add SEC_ASSEMBLER_SHF_MASK" on x86-binutils
implements this, it just needs some fix ups to apply cleanly.
Note that when .section is used without any section attributes
specified, then the non-SHF_GNU_RETAIN section is switched to.
I would expect it to switch to the most recently switched to section.
After applying your above SEC_ASSEMBLER_SHF_MASK commit, the following
assembly code:
.section .data.foo,"aw"
.word 0
.section .data.foo
.word 0
.section .data.foo,"awR"
.word 0
.section .data.foo
.word 0
results in the following section layout
sh_size
[ 4] .data.foo PROGBITS 0000000000000000 000040 000006 00 WA 0 0 1
[ 5] .data.foo PROGBITS 0000000000000000 000046 000002 00 WAR 0 0 1
As you can see, the final ".section .data.foo" instance has been
associated with the non-SHF_GNU_RETAIN section.
To align with this GCC patch, the final ".section .data.foo" directive
should in fact switch to the most recently switched to section, which
would be the SHF_GNU_RETAIN instance of .data.foo.
I've included some other comments inline with the patch
Thanks,
Jozef
>
> gcc/
>
> PR other/98121
> * output.h (switch_to_section): Add a tree argument, default to
> nullptr.
> * varasm.c (get_section): If the SECTION_RETAIN bit doesn't match,
> return and switch to a new section later.
> (assemble_start_function): Pass decl to switch_to_section.
> (assemble_variable): Likewise.
> (switch_to_section): If the SECTION_RETAIN bit doesn't match,
> switch to a new section.
>
> gcc/testsuite/
>
> PR other/98121
> * c-c++-common/attr-used-5.c: New test.
> * c-c++-common/attr-used-6.c: Likewise.
> * c-c++-common/attr-used-7.c: Likewise.
> * c-c++-common/attr-used-8.c: Likewise.
> ---
> gcc/output.h | 2 +-
> gcc/testsuite/c-c++-common/attr-used-5.c | 26 ++++++++++++++++++++++
> gcc/testsuite/c-c++-common/attr-used-6.c | 26 ++++++++++++++++++++++
> gcc/testsuite/c-c++-common/attr-used-7.c | 8 +++++++
> gcc/testsuite/c-c++-common/attr-used-8.c | 8 +++++++
> gcc/varasm.c | 28 ++++++++++++++++++++----
> 6 files changed, 93 insertions(+), 5 deletions(-)
> create mode 100644 gcc/testsuite/c-c++-common/attr-used-5.c
> create mode 100644 gcc/testsuite/c-c++-common/attr-used-6.c
> create mode 100644 gcc/testsuite/c-c++-common/attr-used-7.c
> create mode 100644 gcc/testsuite/c-c++-common/attr-used-8.c
>
> diff --git a/gcc/output.h b/gcc/output.h
> index fa8ace1f394..1f9af46da1d 100644
> --- a/gcc/output.h
> +++ b/gcc/output.h
> @@ -548,7 +548,7 @@ extern void switch_to_other_text_partition (void);
> extern section *get_cdtor_priority_section (int, bool);
>
> extern bool unlikely_text_section_p (section *);
> -extern void switch_to_section (section *);
> +extern void switch_to_section (section *, tree = nullptr);
> extern void output_section_asm_op (const void *);
>
> extern void record_tm_clone_pair (tree, tree);
> diff --git a/gcc/testsuite/c-c++-common/attr-used-5.c
> b/gcc/testsuite/c-c++-common/attr-used-5.c
> new file mode 100644
> index 00000000000..9fc0d3834e9
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/attr-used-5.c
> @@ -0,0 +1,26 @@
> +/* { dg-do compile } */
> +/* { dg-options "-Wall -O2" } */
> +
> +struct dtv_slotinfo_list
> +{
> + struct dtv_slotinfo_list *next;
> +};
> +
> +extern struct dtv_slotinfo_list *list;
> +
> +static int __attribute__ ((section ("__libc_freeres_fn")))
> +free_slotinfo (struct dtv_slotinfo_list **elemp)
> +{
> + if (!free_slotinfo (&(*elemp)->next))
> + return 0;
> + return 1;
> +}
> +
> +__attribute__ ((used, section ("__libc_freeres_fn")))
> +static void free_mem (void)
> +{
> + free_slotinfo (&list);
> +}
> +
> +/* { dg-final { scan-assembler "__libc_freeres_fn,\"ax\"" { target
> R_flag_in_section } } } */
> +/* { dg-final { scan-assembler "__libc_freeres_fn,\"axR\"" { target
> R_flag_in_section } } } */
I think a new copy of the following test should be made to check that a
static free_mem will be removed by GCC, even if another decl in the same
section is marked used. This clarifies behavior Florian was concerned
about in PR/98121.
> diff --git a/gcc/testsuite/c-c++-common/attr-used-6.c
> b/gcc/testsuite/c-c++-common/attr-used-6.c
> new file mode 100644
> index 00000000000..4526a692ee4
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/attr-used-6.c
> @@ -0,0 +1,26 @@
> +/* { dg-do compile } */
> +/* { dg-options "-Wall -O2" } */
> +
> +struct dtv_slotinfo_list
> +{
> + struct dtv_slotinfo_list *next;
> +};
> +
> +extern struct dtv_slotinfo_list *list;
> +
> +static int __attribute__ ((used, section ("__libc_freeres_fn")))
> +free_slotinfo (struct dtv_slotinfo_list **elemp)
> +{
> + if (!free_slotinfo (&(*elemp)->next))
> + return 0;
> + return 1;
> +}
> +
> +__attribute__ ((section ("__libc_freeres_fn")))
If you make this "static void free_mem" and...
> +void free_mem (void)
> +{
> + free_slotinfo (&list);
> +}
> +
... change this to 'scan-assembler-not "free_mem"'.
> +/* { dg-final { scan-assembler "__libc_freeres_fn\n" } } */
> +/* { dg-final { scan-assembler "__libc_freeres_fn,\"axR\"" { target
> R_flag_in_section } } } */
> diff --git a/gcc/testsuite/c-c++-common/attr-used-7.c
> b/gcc/testsuite/c-c++-common/attr-used-7.c
> new file mode 100644
> index 00000000000..fba2706ffc1
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/attr-used-7.c
> @@ -0,0 +1,8 @@
> +/* { dg-do compile } */
> +/* { dg-options "-Wall -O2" } */
> +
> +int __attribute__((used,section(".data.foo"))) foo2 = 2;
> +int __attribute__((section(".data.foo"))) foo1 = 1;
> +
> +/* { dg-final { scan-assembler ".data.foo,\"aw\"" { target R_flag_in_section
> } } } */
> +/* { dg-final { scan-assembler ".data.foo,\"awR\"" { target
> R_flag_in_section } } } */
> diff --git a/gcc/testsuite/c-c++-common/attr-used-8.c
> b/gcc/testsuite/c-c++-common/attr-used-8.c
> new file mode 100644
> index 00000000000..c8d65f65033
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/attr-used-8.c
> @@ -0,0 +1,8 @@
> +/* { dg-do compile } */
> +/* { dg-options "-Wall -O2" } */
> +
> +int __attribute__((section(".data.foo"))) foo1 = 1;
> +int __attribute__((used,section(".data.foo"))) foo2 = 2;
> +
> +/* { dg-final { scan-assembler ".data.foo\n" { target R_flag_in_section } }
> } */
> +/* { dg-final { scan-assembler ".data.foo,\"awR\"" { target
> R_flag_in_section } } } */
> diff --git a/gcc/varasm.c b/gcc/varasm.c
> index 961d2d6fe3b..7271705198c 100644
> --- a/gcc/varasm.c
> +++ b/gcc/varasm.c
> @@ -339,6 +339,11 @@ get_section (const char *name, unsigned int flags, tree
> decl,
> sect->common.flags |= (SECTION_WRITE | SECTION_RELRO);
> return sect;
> }
> + /* If the SECTION_RETAIN bit doesn't match, return and switch
> + to a new section later. */
> + if ((sect->common.flags & SECTION_RETAIN)
> + != (flags & SECTION_RETAIN))
> + return sect;
> /* Sanity check user variables for flag changes. */
> if (sect->named.decl != NULL
> && DECL_P (sect->named.decl)
> @@ -1849,7 +1854,7 @@ assemble_start_function (tree decl, const char *fnname)
>
> /* Switch to the correct text section for the start of the function. */
>
> - switch_to_section (function_section (decl));
> + switch_to_section (function_section (decl), decl);
> if (crtl->has_bb_partition && !hot_label_written)
> ASM_OUTPUT_LABEL (asm_out_file, crtl->subsections.hot_section_label);
>
> @@ -2345,7 +2350,7 @@ assemble_variable (tree decl, int top_level
> ATTRIBUTE_UNUSED,
> && (strcmp (sect->named.name, ".vtable_map_vars") == 0))
> handle_vtv_comdat_section (sect, decl);
> else
> - switch_to_section (sect);
> + switch_to_section (sect, decl);
> if (align > BITS_PER_UNIT)
> ASM_OUTPUT_ALIGN (asm_out_file, floor_log2 (align / BITS_PER_UNIT));
> assemble_variable_contents (decl, name, dont_output_data,
> @@ -7712,10 +7717,25 @@ output_section_asm_op (const void *directive)
> the current section is NEW_SECTION. */
>
> void
> -switch_to_section (section *new_section)
> +switch_to_section (section *new_section, tree decl)
> {
> if (in_section == new_section)
> - return;
> + {
> + if (HAVE_GAS_SHF_GNU_RETAIN
> + && decl != nullptr
> + && (!!DECL_PRESERVE_P (decl)
> + != !!(new_section->common.flags & SECTION_RETAIN)))
> + {
> + /* If the SECTION_RETAIN bit doesn't match, switch to a new
> + section. */
> + if (DECL_PRESERVE_P (decl))
> + new_section->common.flags |= SECTION_RETAIN;
> + else
> + new_section->common.flags &= ~SECTION_RETAIN;
> + }
> + else
> + return;
> + }
>
> if (new_section->common.flags & SECTION_FORGET)
> in_section = NULL;
> --
> 2.28.0
>