Howdy, I was just looking at cleaning up the pthreads implementation but I don't think that it's possible to have our pthreads implementation without providing binary compatibility with the pthread implementation we're hijacking. The reason is that the mutex and rwlock objects are supposed to be opaque to the programmer but in fact they are not opaque to the compiler.
According to the standard, one doesn't have to call pthread_{mutex,rwlock}_init before using a {mutex,rwlock} as would be required with a totally opaque type. The standard allows for static initialization of the mutex and rwlock using PTHREAD_{MUTEX,RWLOCK}_INITIALIZER if you don't want any special attributes.
libc does just this with its rwlock for gettext's global data. If you look at the ream of FIXME:pthread_rwlock_{rd,un}lock that sometimes come out starting wine you'll see that there's no call to pthread_rwlock_init. Of course, this stuff is safe because there's generally no writers wanting the pthread_rwlock_wrlock.
So our current pthreads mutex implementation works in Linux by the skin of its teeth, becuase the first field in the mutex is reserved and initialized to 0 (in both the static and pthread_mutex_init cases), provided the version of LinuxThreads is new enough and a pointer is the same size as an int. Now I assume that this would mean it fails curiously for things that don't use LinuxThreads. This would also seem to mean, since we need binary compatibility, that things could even fail if someone upgrades their libc :(
BTW, our check is presently basically only on __GLIBC__. Flavour me ignorant, and lazy, but I would assume that this not a Linux only symbol. So in fact the wine pthreads implementation might be already used incorrectly.
Reader/Writer locks can be made to work the same way, as the mutex, since there are several fields which are initialized to NULL and we can then HeapAlloc our own structure off that field. We can then come up with weird and wonderful exceptions for the wonky cases that are sure to exist in some implementation of pthreads.
Can anyone see a nice way out of this that I'm remaining ignorant to? I can't even think of a runtime way of detecting if our implementation is going to work. All in all I'm not too enthusiatic about the potential maintenance nightmare that this may provide but it would seem the only way is to have different mutex and rwlock structures depending on the pthreads implementation we're hijacking.
Some code if people are more interested in details:
From glibc 2.2.4
/* Mutexes (not abstract because of PTHREAD_MUTEX_INITIALIZER). */ /* (The layout is unnatural to maintain binary compatibility with earlier releases of LinuxThreads.) */ typedef struct { int __m_reserved; /* Reserved for future use */ int __m_count; /* Depth of recursive locking */ _pthread_descr __m_owner; /* Owner thread (if recursive or errcheck) */ int __m_kind; /* Mutex kind: fast, recursive or errcheck */ struct _pthread_fastlock __m_lock; /* Underlying fast lock */ } pthread_mutex_t;
#define PTHREAD_MUTEX_INITIALIZER \ {0, 0, 0, PTHREAD_MUTEX_TIMED_NP, __LOCK_INITIALIZER}
What WINE uses (scheduler/pthread.c):
/* assume that pthread_mutex_t has room for at least one pointer, * and hope that the users of pthread_mutex_t considers it opaque * (never checks what's in it) */ typedef struct { CRITICAL_SECTION *critsect; } *wine_mutex;
rwlocks on the other hand are not so lucky as there's no unused bits:
From glibc-2.2.4
/* Read-write locks. */ typedef struct _pthread_rwlock_t { struct _pthread_fastlock __rw_lock; /* Lock to guarantee mutual exclusion */ int __rw_readers; /* Number of readers */ _pthread_descr __rw_writer; /* Identity of writer, or NULL if none */ _pthread_descr __rw_read_waiting; /* Threads waiting for reading */ _pthread_descr __rw_write_waiting; /* Threads waiting for writing */ int __rw_kind; /* Reader/Writer preference selection */ int __rw_pshared; /* Shared between processes or not */ } pthread_rwlock_t;
# define PTHREAD_RWLOCK_INITIALIZER \ { __LOCK_INITIALIZER, 0, NULL, NULL, NULL, \ PTHREAD_RWLOCK_DEFAULT_NP, PTHREAD_PROCESS_PRIVATE }
Ciao, Peter hunnise@yahoo.com
__________________________________________________ Do You Yahoo!? Yahoo! GeoCities - quick and easy web site hosting, just $8.95/month. http://geocities.yahoo.com/ps/info1
On Wed, 21 Nov 2001, Peter Hunnisett wrote:
So our current pthreads mutex implementation works in Linux by the skin of its teeth, becuase the first field in the mutex is reserved and initialized to 0 (in both the static and pthread_mutex_init cases), provided the version of LinuxThreads is new enough and a pointer is the same size as an int.
I know that...
Now I assume that this would mean it fails curiously for things that don't use LinuxThreads.
Which of those things may be of concern?
This would also seem to mean, since we need binary compatibility, that things could even fail if someone upgrades their libc :(
Doubtful, libc is typically backwards compatible.
BTW, our check is presently basically only on __GLIBC__. Flavour me ignorant, and lazy, but I would assume that this not a Linux only symbol.
Well, use whatever definition suits you, if you have any other systems with GNU libc that can run Wine.
Reader/Writer locks can be made to work the same way, as the mutex, since there are several fields which are initialized to NULL and we can then HeapAlloc our own structure off that field. We can then come up with weird and wonderful exceptions for the wonky cases that are sure to exist in some implementation of pthreads.
I don't expect Wine to run on *that* many flavors... "hijacking" pthreads is probably only *really* necessary on x86, on other architectures Wine may as well use the native pthreads, so that already limits us to the unix variants that run on x86...
rwlocks on the other hand are not so lucky as there's no unused bits:
From glibc-2.2.4
/* Read-write locks. */ typedef struct _pthread_rwlock_t { struct _pthread_fastlock __rw_lock; /* Lock to guarantee mutual exclusion */ int __rw_readers; /* Number of readers */ _pthread_descr __rw_writer; /* Identity of writer, or NULL if none */ _pthread_descr __rw_read_waiting; /* Threads waiting for reading */ _pthread_descr __rw_write_waiting; /* Threads waiting for writing */ int __rw_kind; /* Reader/Writer preference selection */ int __rw_pshared; /* Shared between processes or not */ } pthread_rwlock_t;
# define PTHREAD_RWLOCK_INITIALIZER \ { __LOCK_INITIALIZER, 0, NULL, NULL, NULL, \ PTHREAD_RWLOCK_DEFAULT_NP, PTHREAD_PROCESS_PRIVATE }
Have you checked what __LOCK_INITIALIZER is?
#define __LT_SPINLOCK_INIT 0 #define __LOCK_INITIALIZER { 0, __LT_SPINLOCK_INIT }
So you can indeed use a copy of wine_mutex on top of a linuxthreads rwlock too, since that overloaded pointer will still be initialized to 0 even if the app used linuxthreads's PTHREAD_RWLOCK_INITIALIZER.