public inbox for libc-alpha@sourceware.org
 help / color / mirror / Atom feed
* GCC miscompilation with __seg_fs
@ 2023-03-13 15:24 Sergey Bugaev
  2023-03-13 15:45 ` Jakub Jelinek
  0 siblings, 1 reply; 4+ messages in thread
From: Sergey Bugaev @ 2023-03-13 15:24 UTC (permalink / raw)
  To: libc-alpha, Jakub Jelinek, Florian Weimer

Hello,

while exploring the generated assembly for an entirely unrelated
reason, I found out that GCC eliminates stores through THREAD_SELF
when it's declared using __seg_fs. This is easily reproducible outside
of glibc; here's a sample reproducer (I've also put it on Godbolt [0]
for easy exploration):

[0]: https://godbolt.org/z/PeYv1TP8Y

#include <stddef.h>

typedef struct
{
  void *tcb;
  int some_member;
} tcbhead_t;

#define THREAD_SELF                                            \
  (*(tcbhead_t * __seg_fs *) offsetof (tcbhead_t, tcb))
#define THREAD_SELF_VOLATILE                                   \
  (*(volatile tcbhead_t * __seg_fs *) offsetof (tcbhead_t, tcb))

#define THREAD_SETMEM_SEG_FS(descr, member, value)             \
  (*(__typeof (descr->member) __seg_fs *)                      \
   offsetof (tcbhead_t, member)) = (value)

#define THREAD_SETMEM_ASM(descr, member, value)                \
  asm volatile ("movl %0, %%fs:%P1" :                          \
                : "rmi" (value),                               \
                "i" (offsetof (tcbhead_t, member)))

void
assign_using_setmem_seg_fs (void)
{
  THREAD_SETMEM_SEG_FS (THREAD_SELF, some_member, 42);
}
// assign_using_setmem_seg_fs:
//         movl $42, %fs:8
//         ret

void
assign_using_setmem_asm (void)
{
  THREAD_SETMEM_ASM (THREAD_SELF, some_member, 42);
}
// assign_using_setmem_asm:
//         movl $42, %fs:8
//         ret

void
assign_through_self (void)
{
  THREAD_SELF->some_member = 42;
}
// assign_through_self:
//         ret

void
assign_through_self_volatile (void)
{
  THREAD_SELF_VOLATILE->some_member = 42;
}
// assign_through_self_volatile:
//         movq %fs:0, %rax
//         movl $42, 8(%rax)
//         ret

void
unused_self_volatile (void)
{
    tcbhead_t *tcb = THREAD_SELF_VOLATILE;
}
// unused_self_volatile:
//         ret

As you can see, assigning to TCB members using THREAD_SETMEM
(implemented either with the inline assembly or __seg_fs) works, but
assigning them through the THREAD_SELF pointer does not. The Hurd port
does that explicitly (see __hurd_local_reply_port), and that is where
I caught the miscompilation in the act. But it is entirely possible
that NPTL suffers from this too, perhaps in code like this:

void
pthread_smth (pthread_t t)
{
  struct pthread *pt = (struct pthread *) t;
  t->some_member = 42;
}

pthread_t
__pthread_self (void)
{
  return (pthread_t) THREAD_SELF;
}

void
foo (void)
{
  pthread_smth (__pthread_self ());
}

One workaround would be redefining THREAD_SELF with volatile (see
THREAD_SELF_VOLATILE above), this makes GCC do the right thing for
assignments and does _not_ prevent optimizations if the value is
unused (see unused_self_volatile above). This does however result in
warnings:

<source>:10:30: warning: initialization discards 'volatile' qualifier
from pointer target type [-Wdiscarded-qualifiers]
   10 | #define THREAD_SELF_VOLATILE (*(volatile tcbhead_t * __seg_fs
*) offsetof (tcbhead_t, tcb))
      |                              ^
<source>:47:22: note: in expansion of macro 'THREAD_SELF_VOLATILE'
   47 |     tcbhead_t *tcb = THREAD_SELF_VOLATILE;
      |                      ^~~~~~~~~~~~~~~~~~~~

What do we do?

Sergey

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: GCC miscompilation with __seg_fs
  2023-03-13 15:24 GCC miscompilation with __seg_fs Sergey Bugaev
@ 2023-03-13 15:45 ` Jakub Jelinek
  2023-03-13 15:53   ` Andrew Pinski
  2023-03-13 16:13   ` Sergey Bugaev
  0 siblings, 2 replies; 4+ messages in thread
From: Jakub Jelinek @ 2023-03-13 15:45 UTC (permalink / raw)
  To: Sergey Bugaev; +Cc: libc-alpha, Florian Weimer

On Mon, Mar 13, 2023 at 06:24:47PM +0300, Sergey Bugaev wrote:
> Hello,
> 
> while exploring the generated assembly for an entirely unrelated
> reason, I found out that GCC eliminates stores through THREAD_SELF
> when it's declared using __seg_fs. This is easily reproducible outside
> of glibc; here's a sample reproducer (I've also put it on Godbolt [0]
> for easy exploration):

Smaller testcase:
typedef struct
{
  void *tcb;
  int some_member;
} tcbhead_t;

void
assign_through_self (void)
{
  (*(tcbhead_t * __seg_fs *) __builtin_offsetof (tcbhead_t, tcb))->some_member = 42;
}

From what I can see, GCC has been DSE removing these since https://gcc.gnu.org/r6-4645
when __seg_fs support has been introduced.
One can work-around it using -fno-delete-null-pointer-checks.
You could also hide the fact that it is based on 0 pointer from the
compiler...

While GCC has targetm.addr_space.zero_address_valid hook (which AFAIK only
x86 overrides), we use it only in very few spots right now.

Feel free to file a bug report in GCC bugzilla, but that won't improve
anything on the already released compilers.

	Jakub


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: GCC miscompilation with __seg_fs
  2023-03-13 15:45 ` Jakub Jelinek
@ 2023-03-13 15:53   ` Andrew Pinski
  2023-03-13 16:13   ` Sergey Bugaev
  1 sibling, 0 replies; 4+ messages in thread
From: Andrew Pinski @ 2023-03-13 15:53 UTC (permalink / raw)
  To: Jakub Jelinek; +Cc: Sergey Bugaev, libc-alpha, Florian Weimer

On Mon, Mar 13, 2023 at 8:46 AM Jakub Jelinek via Libc-alpha
<libc-alpha@sourceware.org> wrote:
>
> On Mon, Mar 13, 2023 at 06:24:47PM +0300, Sergey Bugaev wrote:
> > Hello,
> >
> > while exploring the generated assembly for an entirely unrelated
> > reason, I found out that GCC eliminates stores through THREAD_SELF
> > when it's declared using __seg_fs. This is easily reproducible outside
> > of glibc; here's a sample reproducer (I've also put it on Godbolt [0]
> > for easy exploration):
>
> Smaller testcase:
> typedef struct
> {
>   void *tcb;
>   int some_member;
> } tcbhead_t;
>
> void
> assign_through_self (void)
> {
>   (*(tcbhead_t * __seg_fs *) __builtin_offsetof (tcbhead_t, tcb))->some_member = 42;
> }
>
> From what I can see, GCC has been DSE removing these since https://gcc.gnu.org/r6-4645
> when __seg_fs support has been introduced.
> One can work-around it using -fno-delete-null-pointer-checks.
> You could also hide the fact that it is based on 0 pointer from the
> compiler...
>
> While GCC has targetm.addr_space.zero_address_valid hook (which AFAIK only
> x86 overrides), we use it only in very few spots right now.
>
> Feel free to file a bug report in GCC bugzilla, but that won't improve
> anything on the already released compilers.

I am 99% sure this is https://gcc.gnu.org/PR102733 or at least related to it.

Thanks,
Andrew Pinski

>
>         Jakub
>

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: GCC miscompilation with __seg_fs
  2023-03-13 15:45 ` Jakub Jelinek
  2023-03-13 15:53   ` Andrew Pinski
@ 2023-03-13 16:13   ` Sergey Bugaev
  1 sibling, 0 replies; 4+ messages in thread
From: Sergey Bugaev @ 2023-03-13 16:13 UTC (permalink / raw)
  To: Jakub Jelinek; +Cc: libc-alpha, Florian Weimer

On Mon, Mar 13, 2023 at 6:45 PM Jakub Jelinek <jakub@redhat.com> wrote:
> You could also hide the fact that it is based on 0 pointer from the
> compiler...

But how would you do that? If we want the generated assembly to say
%fs:0, the compiler needs to statically const-propagate the pointer to
zero. If that's not important, this seems to do the trick:

static inline size_t
zero (void)
{
  size_t res;
  asm ("xor %0, %0" : "=r" (res));
  return res;
}

#define THREAD_SELF_HIDDEN                                     \
  (*(tcbhead_t * __seg_fs *) zero ())

void
assign_through_self_hidden (void)
{
  THREAD_SELF_HIDDEN->some_member = 42;
}
// assign_through_self_hidden:
//         xor %rax, %rax
//         movq    %fs:(%rax), %rax
//         movl    $42, 8(%rax)
//         ret

FWIW, without __seg_fs (where dereferencing a zero ptr is actual UB),
assigning through *(int *) 0 is enough for GCC to consider the rest of
the code dead, whereas an assignment through **(int **) 0 does not
trigger that, but the store is still eliminated:

void some_external_call (void);

void
deref_nullptr (void)
{
  *(int *) 0 = 42;
  some_external_call ();
}
// deref_nullptr:
//         movl    $0, 0
//         ud2

void
indirect_deref_nullptr (void)
{
  **(int **) 0 = 42;
  some_external_call ();
}
// indirect_deref_nullptr:
//         jmp     some_external_call

> Feel free to file a bug report in GCC bugzilla, but that won't improve
> anything on the already released compilers.

I don't even have a GCC bugzilla account, so please file it yourself.
But yes, this needs to be worked around in glibc in any case.

Sergey

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2023-03-13 16:13 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-03-13 15:24 GCC miscompilation with __seg_fs Sergey Bugaev
2023-03-13 15:45 ` Jakub Jelinek
2023-03-13 15:53   ` Andrew Pinski
2023-03-13 16:13   ` Sergey Bugaev

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).