GCC miscompilation with __seg_fs
Sergey Bugaev
bugaevc@gmail.com
Mon Mar 13 15:24:47 GMT 2023
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
More information about the Libc-alpha
mailing list