简体   繁体   中英

Why pthread_self is marked with attribute(const)?

In Glibc's pthread.h the pthread_self function is declared with the const attribute:

extern pthread_t pthread_self (void) __THROW __attribute__ ((__const__));

In GCC that attribute means :

Many functions do not examine any values except their arguments, and have no effects except the return value. Basically this is just slightly more strict class than the pure attribute below, since function is not allowed to read global memory.

I wonder how that's supposed to be? Since it does not take any argument, pthread_self is therefore allowed only to always return the same value , which is obviously not the case. That is, I would have expected pthread_self to read global memory, and therefore eventually be marked as pure instead:

Many functions have no effects except the return value and their return value depends only on the parameters and/or global variables. Such a function can be subject to common subexpression elimination and loop optimization just as an arithmetic operator would be. These functions should be declared with the attribute pure.

The implementation on x86-64 seems to be actually reading global memory:

# define THREAD_SELF \
  ({ struct pthread *__self;                                           \
     asm ("mov %%fs:%c1,%0" : "=r" (__self)                            \
          : "i" (offsetof (struct pthread, header.self)));             \
     __self;})

pthread_t
__pthread_self (void)
{
  return (pthread_t) THREAD_SELF;
}
strong_alias (__pthread_self, pthread_self)

Is this a bug or am I not seeing something?

The attribute was most likely added in the assumption that GCC would only use it locally (within a function), and would never be able to use it for inter-procedural optimizations. Today, some of Glibc developers are questioning the correctness of the attribute exactly because powerful inter-procedural optimization could, potentially, lead to miscompilation; quoting post by Torvald Riegel to Glibc developers' mailing list ,

The const attribute is specified as asserting that the function does not examine any data except the arguments. __errno_location has no arguments, so it would have to return the same values every time . This works in a single-threaded program, but not in a multi-threaded one. Thus, I think that strictly speaking, it should not be const.

We could argue that this magically is meant to always be in the context of a specific thread. Ignoring that GCC doesn't define threads itself (especially in something like NPTL which is about creating a notion of threads), we could still assume that this works because in practice, the compiler and its passes can't leak knowledge across a function used in one thread and other one used in another thread.

( __errno_location() and pthread_self() both are marked with __attribute__((const)) and receive no arguments).

Here's a small example that could plausibly be miscompiled with powerful interprocedural analysis:

#include <pthread.h>
#include <errno.h>
#include <stdlib.h>

static void *errno_pointer;

static void *thr(void *unused)
{
  if (!errno_pointer || errno_pointer == &errno)
    abort();
  return 0;
}

int main()
{
  errno_pointer = &errno;
  pthread_t t;
  pthread_create(&t, 0, thr, 0);
  pthread_join(t, 0);
}

(the compiler can observe that errno_pointer is static, it does not escape the translation unit, and the only store into it assigns the same "const" value, given by __errno_location() , that is tested in thr() ). I've used this example in my email asking to improve documentation of pure/const attributes , but unfortunately it didn't get much traction.

I wonder how that's supposed to be?

This attribute is telling the compiler that in a given context pthread_self will always return the same value. In other words, the two loops below are exactly equivalent, and the compiler is allowed to optimize out the second (and all subsequent) calls to pthread_self :

// loop A
std::map<pthread_t, int> m;
for (int j = 0; j < 1000; ++j)
  m[pthread_self()] += 1;

// loop B
std::map<pthread_t, int> m;
const pthread_t self = pthread_self();
for (int j = 0; j < 1000; ++j)
  m[self] += 1;

The implementation on x86-64 seems to be actually reading global memory

No, it does not. It reads thread-local memory.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM