// -*- C++ -*- (c) 2013 Milan Lenco // (c) 2014-2016 Vladimír Štill // (c) 2016 Henrich Lauko // (c) 2016 Jan Mrázek /* Includes */ #include #include #include #include #include #include #include #include #include #include #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wgcc-compat" // thresholds #define MILLIARD 1000000000 // bit masks #define _THREAD_ATTR_DETACH_MASK 0x1 #define _MUTEX_ATTR_TYPE_MASK 0x3 #define _RWLOCK_ATTR_SHARING_MASK 0x1 struct CleanupHandler { void ( *routine )( void * ); void *arg; CleanupHandler *next; }; enum SleepingOn { NotSleeping = 0, Condition, Barrier }; typedef unsigned short ushort; struct _PThread { // (user-space) information maintained for every (running) // thread _PThread() noexcept { std::memset( this, 0, sizeof( _PThread ) ); } // avoid accidental copies _PThread( const _PThread & ) = delete; _PThread( _PThread && ) = delete; void *result; pthread_mutex_t *waiting_mutex; CleanupHandler *cleanup_handlers; union { pthread_cond_t *condition; pthread_barrier_t *barrier; }; bool running : 1; bool detached : 1; SleepingOn sleeping : 2; bool cancelled : 1; bool cancel_state : 1; bool cancel_type : 1; bool is_main : 1; bool deadlocked : 1; unsigned sigmaxused : 5; // at most 32 signals void setSleeping( pthread_cond_t *cond ) noexcept { sleeping = Condition; condition = cond; } void setSleeping( pthread_barrier_t *bar ) noexcept { sleeping = Barrier; barrier = bar; } }; using Destructor = void (*)( void * ); struct _PthreadTLSDestructors { _PthreadTLSDestructors() noexcept = default; _PthreadTLSDestructors( const _PthreadTLSDestructors & ) noexcept = delete; _PthreadTLSDestructors( _PthreadTLSDestructors && ) noexcept = delete; int count() noexcept { return __vm_obj_size( _dtors ) / sizeof( Destructor ); } int getFirstAvailable() noexcept { int k = -1; int c = count(); for ( int i = 0; i < c; ++i ) if ( _dtors[ i ] == nullptr ) { k = i; break; } if ( k < 0 ) { k = c; __vm_obj_resize( _dtors, sizeof( Destructor ) * (k + 1) ); } return k; } void shrink() noexcept { int toDrop = 0; int c = count(); for ( int i = c - 1; i >= 0; --i ) { if ( _dtors[ i ] == nullptr ) ++toDrop; else break; } if ( toDrop ) __vm_obj_resize( _dtors, std::max( size_t( 1 ), (c - toDrop) * sizeof( Destructor ) ) ); } void init() noexcept { assert( !_dtors ); _dtors = static_cast< Destructor * >( __vm_obj_make( 1 ) ); // placeholder so that resize works } Destructor &operator[]( size_t x ) noexcept { return _dtors[ x ]; } Destructor *_dtors; }; static _PthreadTLSDestructors tlsDestructors; struct _PthreadTLS { _PthreadTLS( const _PthreadTLS & ) noexcept = delete; _PthreadTLS( _PthreadTLS && ) noexcept = delete; _PThread *thread; void *keys[0]; void *raw() noexcept { return reinterpret_cast< char * >( this ) - sizeof( _DiOS_TLS ); } static int size( int cnt ) noexcept { return sizeof( struct _DiOS_TLS ) + sizeof( _PThread * ) + cnt * sizeof( void * ); } int keyCount() noexcept { return (__vm_obj_size( raw() ) - size( 0 )) / sizeof( void * ); } void makeFit( int count ) noexcept { int now = keyCount(); if ( count > now ) { __vm_obj_resize( raw(), size( count ) ); for ( int i = now; i < count; ++i ) keys[ i ] = nullptr; } } void shrink() noexcept { int count = keyCount(); int toDrop = 0; for ( int i = count - 1; i >= 0; --i ) if ( keys[ i ] == nullptr ) ++toDrop; else break; if ( toDrop ) __vm_obj_resize( raw(), size( count - toDrop ) ); } void *getKey( int key ) noexcept { assert( key >= 0 && key < tlsDestructors.count() ); if ( key >= keyCount() ) return nullptr; return keys[ key ]; } void setKey( int key, const void *value ) noexcept { assert( key >= 0 && key < tlsDestructors.count() ); const int c = keyCount(); if ( value == nullptr && key >= c ) return; if ( key >= c ) makeFit( key + 1 ); keys[ key ] = const_cast< void * >( value ); if ( value == nullptr ) shrink(); } struct TLSInfo { TLSInfo( unsigned index, _PthreadTLS *tls ) noexcept : index( index ), tls( tls ) { } void *getData() noexcept { return tls->getKey( index ); } void setData( const void *v ) noexcept { return tls->setKey( index, v ); } Destructor destructor() noexcept { return tlsDestructors[ index ]; } unsigned index; _PthreadTLS *tls; }; struct TLSIter { TLSIter( unsigned index, _PthreadTLS *tls ) noexcept : index( index ), tls( tls ) { } TLSInfo operator*() noexcept { return TLSInfo( index, tls ); } TLSIter &operator++() noexcept { ++index; return *this; } bool operator!=( const TLSIter &o ) const noexcept { return o.index != index || o.tls != tls; } unsigned index; _PthreadTLS *tls; }; TLSIter begin() noexcept { return TLSIter( 0, this ); } TLSIter end() noexcept { return TLSIter( keyCount(), this ); } }; static inline _PthreadTLS &tls( _DiOS_ThreadHandle tid ) noexcept { return *reinterpret_cast< _PthreadTLS * >( &( tid->data ) ); } static inline _PThread &getThread( pthread_t tid ) noexcept { return *tls( tid ).thread; } static inline _PThread &getThread() noexcept { return getThread( __dios_get_thread_handle() ); } template< typename Yield > static void iterateThreads( Yield yield ) noexcept { auto *threads = __dios_get_process_threads(); int cnt = __vm_obj_size( threads ) / sizeof( _DiOS_ThreadHandle ); for ( int i = 0; i < cnt; ++i ) yield( threads[ i ] ); } /* Process */ int pthread_atfork( void ( * )( void ), void ( * )( void ), void ( * )( void ) ) noexcept { /* TODO */ return 0; } static void __init_thread( const _DiOS_ThreadHandle gtid, const pthread_attr_t attr ) noexcept { __dios_assert( gtid ); if ( __vm_obj_size( gtid ) < _PthreadTLS::size( 0 ) ) __vm_obj_resize( gtid, _PthreadTLS::size( 0 ) ); auto *thread = static_cast< _PThread * >( __vm_obj_make( sizeof( _PThread ) ) ); new ( thread ) _PThread(); tls( gtid ).thread = thread; // initialize thread metadata thread->running = true; thread->detached = ( ( attr & _THREAD_ATTR_DETACH_MASK ) == PTHREAD_CREATE_DETACHED ); thread->condition = nullptr; thread->cancel_state = PTHREAD_CANCEL_ENABLE; thread->cancel_type = PTHREAD_CANCEL_DEFERRED; thread->sigmaxused = 0; } void __pthread_initialize() noexcept { // initialize implicitly created main thread tlsDestructors.init(); __init_thread( __dios_get_thread_handle(), PTHREAD_CREATE_DETACHED ); getThread().is_main = true; } // this is not run when thread's main returns! void _run_cleanup_handlers() noexcept { _PThread &thread = getThread(); CleanupHandler *handler = thread.cleanup_handlers; thread.cleanup_handlers = NULL; CleanupHandler *next; while ( handler ) { next = handler->next; handler->routine( handler->arg ); __vm_obj_free( handler ); handler = next; } } static void _clean_and_become_zombie( __dios::FencedInterruptMask &mask, _DiOS_ThreadHandle tid ) noexcept; void _cancel( __dios::FencedInterruptMask &mask ) noexcept { _DiOS_ThreadHandle tid = __dios_get_thread_handle(); _PThread &thread = getThread( tid ); thread.sleeping = NotSleeping; // call all cleanup handlers _run_cleanup_handlers(); _clean_and_become_zombie( mask, tid ); } bool _canceled() noexcept { return getThread().cancelled; } template < bool cancelPoint, typename Cond > static void _wait( __dios::FencedInterruptMask &mask, Cond &&cond ) noexcept __attribute__( ( __always_inline__, __flatten__ ) ) { while ( cond() && ( !cancelPoint || !_canceled() ) ) mask.without( [] { }, true ); // break mask to allow control flow interrupt if ( cancelPoint && _canceled() ) _cancel( mask ); } template < typename Cond > static void waitOrCancel( __dios::FencedInterruptMask &mask, Cond &&cond ) noexcept __attribute__( ( __always_inline__, __flatten__ ) ) { return _wait< true >( mask, std::forward< Cond >( cond ) ); } template < typename Cond > static void wait( __dios::FencedInterruptMask &mask, Cond &&cond ) noexcept __attribute__( ( __always_inline__, __flatten__ ) ) { return _wait< false >( mask, std::forward< Cond >( cond ) ); } static void _clean_and_become_zombie( __dios::FencedInterruptMask &mask, _DiOS_ThreadHandle tid ) noexcept { _PThread &thread = getThread( tid ); // An optional destructor function may be associated with each key // value. At thread exit, if a key value has a non-NULL destructor // pointer, and the thread has a non-NULL value associated with that key, // the value of the key is set to NULL, and then the function pointed to is // called with the previously associated value as its sole argument. The // order of destructor calls is unspecified if more than one destructor // exists for a thread when it exits. // // If, after all the destructors have been called for all non-NULL values // with associated destructors, there are still some non-NULL values with // associated destructors, then the process is repeated. If, after // at least {PTHREAD_DESTRUCTOR_ITERATIONS} iterations of destructor // calls for outstanding non-NULL values, there are still some non-NULL // values with associated destructors, implementations may stop calling // destructors, or they may continue calling destructors until no // non-NULL values with associated destructors exist, even though this // might result in an infinite loop. int iter = 0; auto &tls = ::tls( tid ); do { for ( auto tld : tls ) { if ( tld.getData() ) { auto *data = tld.getData(); tld.setData( nullptr ); tld.destructor()( data ); } } ++iter; } while ( iter <= PTHREAD_DESTRUCTOR_ITERATIONS && std::find_if( tls.begin(), tls.end(), []( auto tls ) { return !tls.getData(); } ) != tls.end() ); thread.running = false; if ( thread.detached ) { // leak &thread so that metadata can be read for use by deadlock // tracking; &thread will be released by DIVINE when all pointers to it // are lost __dios_kill_thread( tid ); } else // wait until detach / join kills us wait( mask, [&] { return true; } ); } /* Internal data types */ struct Entry { void *( *entry )( void * ); void *arg; }; // no nexcept here, avoid adding landingpads and filters extern "C" void __pthread_entry( void *_args ) { __dios::FencedInterruptMask mask; Entry *args = static_cast< Entry * >( _args ); auto tid = __dios_get_thread_handle(); _PThread &thread = getThread( tid ); // copy arguments void *arg = args->arg; auto entry = args->entry; __vm_obj_free( _args ); __vm_obj_shared( arg ); // call entry function mask.without( [&] { thread.result = entry( arg ); } ); assert( thread.sleeping == false ); _clean_and_become_zombie( mask, tid ); } int pthread_create( pthread_t *ptid, const pthread_attr_t *attr, void *( *entry )( void * ), void *arg ) noexcept { __dios::FencedInterruptMask mask; // test input arguments __dios_assert( entry ); if ( ptid == NULL || entry == NULL ) return EINVAL; // create new thread and pass arguments to the entry wrapper Entry *args = static_cast< Entry * >( __vm_obj_make( sizeof( Entry ) ) ); args->entry = entry; args->arg = arg; auto tid = __dios_start_thread( __pthread_entry, static_cast< void * >( args ), _PthreadTLS::size( 0 ) ); // init thread here, before it has first chance to run __init_thread( tid, attr == nullptr ? PTHREAD_CREATE_JOINABLE : *attr ); *ptid = tid; return 0; } int _pthread_join( __dios::FencedInterruptMask &mask, pthread_t gtid, void **result ) noexcept { _PThread &thread = getThread( gtid ); if ( gtid == __dios_get_thread_handle() ) return EDEADLK; if ( thread.detached ) return EINVAL; // wait for the thread to finnish waitOrCancel( mask, [&] { return thread.running; } ); if ( thread.detached ) // meanwhile detached return EINVAL; // copy result if ( result ) { if ( thread.cancelled ) *result = PTHREAD_CANCELED; else *result = thread.result; } // kill the thread so that it does not pollute state space by ending // nondeterministically, but leak it (see _clean_and_become_zombie) __dios_kill_thread( gtid ); return 0; } void pthread_exit( void *result ) noexcept { __dios::FencedInterruptMask mask; auto gtid = __dios_get_thread_handle(); _PThread &thread = getThread( gtid ); thread.result = result; if ( thread.is_main ) iterateThreads( [&]( _DiOS_ThreadHandle tid ) { _pthread_join( mask, tid, nullptr ); // joining self is detected and ignored } ); _run_cleanup_handlers(); _clean_and_become_zombie( mask, gtid ); } int pthread_join( pthread_t gtid, void **result ) noexcept { __dios::FencedInterruptMask mask; return _pthread_join( mask, gtid, result ); } int pthread_detach( pthread_t gtid ) noexcept { __dios::FencedInterruptMask mask; _PThread &thread = getThread( gtid ); if ( thread.detached ) return EINVAL; bool ended = !thread.running; thread.detached = true; if ( ended ) { // kill the thread so that it does not pollute state space by ending // nondeterministically, but leak the thread descritor (see // _clean_and_become_zombie) __dios_kill_thread( gtid ); } return 0; } /* Thread attributes */ /* pthread_attr_t representation: ------------------------------ | *free* | detach state: 1 bit | ------------------------------ */ int pthread_attr_destroy( pthread_attr_t * ) noexcept { __dios::FencedInterruptMask mask; return 0; } int pthread_attr_init( pthread_attr_t *attr ) noexcept { __dios::FencedInterruptMask mask; *attr = 0; return 0; } int pthread_attr_getdetachstate( const pthread_attr_t *attr, int *state ) noexcept { __dios::FencedInterruptMask mask; if ( attr == NULL || state == NULL ) return EINVAL; *state = *attr & _THREAD_ATTR_DETACH_MASK; return 0; } int pthread_attr_getguardsize( const pthread_attr_t *, size_t * ) noexcept { /* TODO */ return 0; } int pthread_attr_getinheritsched( const pthread_attr_t *, int * ) noexcept { /* TODO */ return 0; } int pthread_attr_getschedparam( const pthread_attr_t *, struct sched_param * ) noexcept { /* TODO */ return 0; } int pthread_attr_getschedpolicy( const pthread_attr_t *, int * ) noexcept { /* TODO */ return 0; } int pthread_attr_getscope( const pthread_attr_t *, int * ) noexcept { /* TODO */ return 0; } int pthread_attr_getstack( const pthread_attr_t *, void **, size_t * ) noexcept { /* TODO */ return 0; } int pthread_attr_getstackaddr( const pthread_attr_t *, void ** ) noexcept { /* TODO */ return 0; } int pthread_attr_getstacksize( const pthread_attr_t *, size_t * ) noexcept { /* TODO */ return 0; } int pthread_attr_setdetachstate( pthread_attr_t *attr, int state ) noexcept { __dios::FencedInterruptMask mask; if ( attr == NULL || ( state & ~_THREAD_ATTR_DETACH_MASK ) ) return EINVAL; *attr &= ~_THREAD_ATTR_DETACH_MASK; *attr |= state; return 0; } int pthread_attr_setguardsize( pthread_attr_t *, size_t ) noexcept { /* TODO */ return 0; } int pthread_attr_setinheritsched( pthread_attr_t *, int ) noexcept { /* TODO */ return 0; } int pthread_attr_setschedparam( pthread_attr_t *, const struct sched_param * ) noexcept { /* TODO */ return 0; } int pthread_attr_setschedpolicy( pthread_attr_t *, int ) noexcept { /* TODO */ return 0; } int pthread_attr_setscope( pthread_attr_t *, int ) noexcept { /* TODO */ return 0; } int pthread_attr_setstack( pthread_attr_t *, void *, size_t ) noexcept { /* TODO */ return 0; } int pthread_attr_setstackaddr( pthread_attr_t *, void * ) noexcept { /* TODO */ return 0; } int pthread_attr_setstacksize( pthread_attr_t *, size_t ) noexcept { /* TODO */ return 0; } /* Thread ID */ pthread_t pthread_self( void ) noexcept { __dios::FencedInterruptMask mask; return __dios_get_thread_handle(); } int pthread_equal( pthread_t t1, pthread_t t2 ) noexcept { return t1 == t2; } /* Scheduler */ int pthread_getconcurrency( void ) noexcept { /* TODO */ return 0; } int pthread_getcpuclockid( pthread_t, clockid_t * ) noexcept { /* TODO */ return 0; } int pthread_getschedparam( pthread_t, int *, struct sched_param * ) noexcept { /* TODO */ return 0; } int pthread_setconcurrency( int ) noexcept { /* TODO */ return 0; } int pthread_setschedparam( pthread_t, int, const struct sched_param * ) noexcept { /* TODO */ return 0; } int pthread_setschedprio( pthread_t, int ) noexcept { /* TODO */ return 0; } /* Mutex */ /* pthread_mutex_t representation: ----------------------------------------------------------------------------------------------- | *free* | initialized?: 1 bit | type: 2 bits | lock counter: 8 bits | (thread_id + 1): 16 bits | ----------------------------------------------------------------------------------------------- */ int _mutex_adjust_count( pthread_mutex_t *mutex, int adj ) noexcept { int count = mutex->__lockCounter; count += adj; if ( count >= ( 1 << 28 ) || count < 0 ) return EAGAIN; mutex->__lockCounter = count; return 0; } void _check_deadlock( pthread_mutex_t *mutex, _PThread &tid ) noexcept { // note: the cycle is detected first time it occurs, therefore it must go // through this mutex, for this reason, we don't need to keep closed set of // visited threads and mutexes. // Furthermore, if the cycle is re-detected it is already marked in // 'deadlocked' attribute of Thread struct. while ( mutex && mutex->__owner ) { _PThread *owner = mutex->__owner; if ( owner == &tid ) { tid.deadlocked = owner->deadlocked = true; return __dios_fault( _VM_Fault::_VM_F_Locking, "Deadlock: mutex cycle closed, circular waiting" ); } if ( owner->deadlocked ) { tid.deadlocked = true; return __dios_fault( _VM_Fault::_VM_F_Locking, "Deadlock: waiting for a deadlocked thread" ); } if ( !owner->running ) { tid.deadlocked = true; return __dios_fault( _VM_Fault::_VM_F_Locking, "Deadlock: mutex locked by a dead thread" ); } mutex = owner->waiting_mutex; } } bool _mutex_can_lock( pthread_mutex_t *mutex, _PThread &thr ) noexcept { return !mutex->__owner || ( mutex->__owner == &thr ); } int _mutex_lock( __dios::FencedInterruptMask &mask, pthread_mutex_t *mutex, bool wait ) noexcept { _DiOS_ThreadHandle gtid = __dios_get_thread_handle(); _PThread &thr = getThread( gtid ); if ( mutex == NULL || !mutex->__initialized ) { return EINVAL; // mutex does not refer to an initialized mutex object } if ( mutex->__owner == &thr ) { // already locked by this thread assert( mutex->__lockCounter ); // count should be > 0 if ( mutex->__type != PTHREAD_MUTEX_RECURSIVE ) { if ( mutex->__type == PTHREAD_MUTEX_ERRORCHECK ) return EDEADLK; else __dios_fault( _VM_Fault::_VM_F_Locking, "Deadlock: Nonrecursive mutex locked again" ); } } if ( !_mutex_can_lock( mutex, thr ) ) { assert( mutex->__lockCounter ); // count should be > 0 if ( !wait ) return EBUSY; } // mark waiting now for wait cycle detection // note: waiting should not ne set in case of unsuccessfull try-lock // (cycle in try-lock is not deadlock, although it might be livelock) // so it must be here after return EBUSY thr.waiting_mutex = mutex; while ( !_mutex_can_lock( mutex, thr ) ) { _check_deadlock( mutex, thr ); mask.without( [] { }, true ); // break mask to allow control flow interrupt } thr.waiting_mutex = NULL; // try to increment lock counter int err = _mutex_adjust_count( mutex, 1 ); if ( err ) return err; // lock the mutex mutex->__owner = &thr; return 0; } int pthread_mutex_destroy( pthread_mutex_t *mutex ) noexcept { __dios::FencedInterruptMask mask; if ( mutex == NULL ) return EINVAL; if ( mutex->__owner ) { // mutex is locked if ( mutex->__type == PTHREAD_MUTEX_ERRORCHECK ) return EBUSY; else __dios_fault( _VM_Fault::_VM_F_Locking, "Locked mutex destroyed" ); } mutex->__initialized = 0; return 0; } int pthread_mutex_init( pthread_mutex_t *mutex, const pthread_mutexattr_t *attr ) noexcept { __dios::FencedInterruptMask mask; if ( mutex == NULL ) return EINVAL; { __dios::DetectFault f( _VM_F_Control ); if ( mutex->__initialized && !f.triggered() ) { __dios_trace_t( "WARN: re-initializing mutex" ); return EBUSY; } } /* bitfield initializers can be only used as such, that is, initializers, * *not* as a right-hand side of an assignment to uninitialised memory */ memset( mutex, 0, sizeof( pthread_mutex_t ) ); pthread_mutex_t init = PTHREAD_MUTEX_INITIALIZER; *mutex = init; if ( attr ) mutex->__type = attr->__type; else mutex->__type = PTHREAD_MUTEX_DEFAULT; return 0; } int pthread_mutex_lock( pthread_mutex_t *mutex ) noexcept { __dios::FencedInterruptMask mask; return _mutex_lock( mask, mutex, 1 ); } int pthread_mutex_trylock( pthread_mutex_t *mutex ) noexcept { __dios::FencedInterruptMask mask; return _mutex_lock( mask, mutex, 0 ); } int pthread_mutex_unlock( pthread_mutex_t *mutex ) noexcept { __dios::FencedInterruptMask mask; _PThread &thr = getThread(); if ( mutex == NULL || !mutex->__initialized ) { return EINVAL; // mutex does not refer to an initialized mutex object } if ( mutex->__owner != &thr ) { // mutex is not locked or it is already locked by another thread assert( mutex->__lockCounter ); // count should be > 0 if ( mutex->__type == PTHREAD_MUTEX_NORMAL ) __dios_fault( _VM_Fault::_VM_F_Locking, "Mutex has to be released by the " "same thread which acquired the " "mutex." ); else return EPERM; // recursive mutex can also detect } int r = _mutex_adjust_count( mutex, -1 ); assert( r == 0 ); if ( !mutex->__lockCounter ) { mutex->__owner = nullptr; // unlock if count == 0 } return 0; } int pthread_mutex_getprioceiling( const pthread_mutex_t *, int * ) noexcept { /* TODO */ return 0; } int pthread_mutex_setprioceiling( pthread_mutex_t *, int, int * ) noexcept { /* TODO */ return 0; } int pthread_mutex_timedlock( pthread_mutex_t *mutex, const struct timespec *abstime ) noexcept { __dios::FencedInterruptMask mask; if ( abstime == NULL || abstime->tv_nsec < 0 || abstime->tv_nsec >= MILLIARD ) { return EINVAL; } // Consider both scenarios, one in which the mutex could not be locked // before the specified timeout expired, and the other in which // pthread_mutex_timedlock behaves basically the same as pthread_mutex_lock. if ( __vm_choose( 2 ) ) { return pthread_mutex_lock( mutex ); } else { return ETIMEDOUT; } } /* Mutex attributes */ /* pthread_mutexattr_t representation: ----------------------------- | *free* | mutex type: 2 bits | ----------------------------- */ int pthread_mutexattr_destroy( pthread_mutexattr_t *attr ) noexcept { __dios::FencedInterruptMask mask; if ( attr == NULL ) return EINVAL; attr->__type = 0; return 0; } int pthread_mutexattr_init( pthread_mutexattr_t *attr ) noexcept { __dios::FencedInterruptMask mask; if ( attr == NULL ) return EINVAL; attr->__type = PTHREAD_MUTEX_DEFAULT; return 0; } int pthread_mutexattr_gettype( const pthread_mutexattr_t *attr, int *value ) noexcept { __dios::FencedInterruptMask mask; if ( attr == NULL || value == NULL ) return EINVAL; *value = attr->__type & _MUTEX_ATTR_TYPE_MASK; return 0; } int pthread_mutexattr_getprioceiling( const pthread_mutexattr_t *, int * ) noexcept { /* TODO */ return 0; } int pthread_mutexattr_getprotocol( const pthread_mutexattr_t *, int * ) noexcept { /* TODO */ return 0; } int pthread_mutexattr_getpshared( const pthread_mutexattr_t *, int * ) noexcept { /* TODO */ return 0; } int pthread_mutexattr_settype( pthread_mutexattr_t *attr, int value ) noexcept { __dios::FencedInterruptMask mask; if ( attr == NULL || ( value & ~_MUTEX_ATTR_TYPE_MASK ) ) return EINVAL; attr->__type = value; return 0; } int pthread_mutexattr_setprioceiling( pthread_mutexattr_t *, int ) noexcept { /* TODO */ return 0; } int pthread_mutexattr_setprotocol( pthread_mutexattr_t *, int ) noexcept { /* TODO */ return 0; } int pthread_mutexattr_setpshared( pthread_mutexattr_t *, int ) noexcept { /* TODO */ return 0; } /* Spin lock */ int pthread_spin_destroy( pthread_spinlock_t * ) noexcept { /* TODO */ return 0; } int pthread_spin_init( pthread_spinlock_t *, int ) noexcept { /* TODO */ return 0; } int pthread_spin_lock( pthread_spinlock_t * ) noexcept { /* TODO */ return 0; } int pthread_spin_trylock( pthread_spinlock_t * ) noexcept { /* TODO */ return 0; } int pthread_spin_unlock( pthread_spinlock_t * ) noexcept { /* TODO */ return 0; } /* Thread specific data */ int pthread_key_create( pthread_key_t *p_key, void ( *destructor )( void * ) ) noexcept { __dios::FencedInterruptMask mask; *p_key = tlsDestructors.getFirstAvailable();; tlsDestructors[ *p_key ] = destructor ?: []( void * ) { }; // TODO: simulate errors? return 0; } int pthread_key_delete( pthread_key_t key ) noexcept { __dios::FencedInterruptMask mask; if ( key >= tlsDestructors.count() ) return EINVAL; iterateThreads( [&]( _DiOS_ThreadHandle tid ) { tls( tid ).setKey( key, nullptr ); tls( tid ).shrink(); } ); tlsDestructors[ key ] = nullptr; tlsDestructors.shrink(); return 0; } int pthread_setspecific( pthread_key_t key, const void *data ) noexcept { __dios::FencedInterruptMask mask; tls( __dios_get_thread_handle() ).setKey( key, data ); return 0; } void *pthread_getspecific( pthread_key_t key ) noexcept { __dios::FencedInterruptMask mask; return tls( __dios_get_thread_handle() ).getKey( key ); } /* Conditional variables */ /* pthread_cond_t representation: { .mutex: address of associated mutex .counter: number of waiting threads: 16 bits .initialized: 1 bit ._pad: 15 bits } */ template < typename CondOrBarrier > int _cond_adjust_count( CondOrBarrier *cond, int adj ) noexcept { int count = cond->__counter; count += adj; assert( count < ( 1 << 16 ) ); assert( count >= 0 ); cond->__counter = count; return count; } template < typename CondOrBarrier > int _destroy_cond_or_barrier( CondOrBarrier *cond ) noexcept { if ( cond == NULL || !cond->__initialized ) return EINVAL; // make sure that no thread is waiting on this condition // (probably better alternative when compared to: return EBUSY) assert( cond->__counter == 0 ); cond->__counter = 0; cond->__initialized = 0; return 0; } int pthread_cond_destroy( pthread_cond_t *cond ) noexcept { __dios::FencedInterruptMask mask; int r = _destroy_cond_or_barrier( cond ); if ( r == 0 ) cond->__mutex = NULL; return r; } int pthread_cond_init( pthread_cond_t *cond, const pthread_condattr_t * /* TODO: cond. attributes */ ) noexcept { __dios::FencedInterruptMask mask; if ( cond == NULL ) return EINVAL; { __dios::DetectFault f( _VM_F_Control ); if ( cond->__initialized && !f.triggered() ) { __dios_trace_t( "WARN: re-initializing conditional variable" ); return EBUSY; } } *cond = ( pthread_cond_t ){.__mutex = NULL, .__initialized = 1, .__counter = 0 }; return 0; } template < typename > static inline SleepingOn _sleepCond() noexcept; template <> inline SleepingOn _sleepCond< pthread_barrier_t >() noexcept { return Barrier; } template <> inline SleepingOn _sleepCond< pthread_cond_t >() noexcept { return Condition; } static inline bool _eqSleepTrait( _PThread &t, pthread_cond_t *cond ) noexcept { return t.condition == cond; } static inline bool _eqSleepTrait( _PThread &t, pthread_barrier_t *bar ) noexcept { return t.barrier == bar; } template< bool broadcast, typename CondOrBarrier > int _cond_signal( CondOrBarrier *cond ) noexcept { if ( cond == NULL || !cond->__initialized ) return EINVAL; int count = cond->__counter; if ( count ) { // some threads are waiting for condition int waiting = 0, wokenup = 0, choice; if ( !broadcast ) { /* non-determinism */ choice = __vm_choose( ( 1 << count ) - 1 ); } iterateThreads( [&]( pthread_t tid ) { _PThread &thread = getThread( tid ); if ( thread.sleeping == _sleepCond< CondOrBarrier >() && _eqSleepTrait( thread, cond ) ) { ++waiting; if ( broadcast || ( ( choice + 1 ) & ( 1 << ( waiting - 1 ) ) ) ) { // wake up the thread thread.sleeping = NotSleeping; thread.condition = NULL; ++wokenup; } } } ); assert( count == waiting ); _cond_adjust_count( cond, -wokenup ); } return 0; } int pthread_cond_signal( pthread_cond_t *cond ) noexcept { __dios::FencedInterruptMask mask; int r = _cond_signal< false >( cond ); if ( r == 0 && cond->__counter == 0 ) cond->__mutex = NULL; // break binding between cond. variable and mutex return r; } int pthread_cond_broadcast( pthread_cond_t *cond ) noexcept { __dios::FencedInterruptMask mask; int r = _cond_signal< true >( cond ); if ( r == 0 && cond->__counter == 0 ) cond->__mutex = NULL; // break binding between cond. variable and mutex return r; } int pthread_cond_wait( pthread_cond_t *cond, pthread_mutex_t *mutex ) noexcept { __dios::FencedInterruptMask mask; _PThread &thread = getThread(); if ( cond == NULL || !cond->__initialized ) return EINVAL; if ( mutex == NULL || !mutex->__initialized ) { return EINVAL; } if ( mutex->__owner != &thread ) { // mutex is not locked or it is already locked by another thread return EPERM; } if ( __vm_choose( 2 ) ) return 0; // simulate spurious wakeup // It is allowed to have one mutex associated with more than one conditional // variable. On the other hand, using more than one mutex for one // conditional variable results in undefined behaviour. if ( cond->__mutex != NULL && cond->__mutex != mutex ) __dios_fault( _VM_Fault::_VM_F_Locking, "Attempted to wait on condition variable using other mutex " "that already in use by this condition variable" ); cond->__mutex = mutex; // fall asleep thread.setSleeping( cond ); _cond_adjust_count( cond, 1 ); // one more thread is waiting for this condition pthread_mutex_unlock( mutex ); // unlock associated mutex // sleeping waitOrCancel( mask, [&] { return thread.sleeping == Condition; } ); // try to lock mutex which was associated to the cond. variable by this // thread // (not the one from current binding, which may have changed) _mutex_lock( mask, mutex, 1 ); return 0; } int pthread_cond_timedwait( pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime ) noexcept { __dios::FencedInterruptMask mask; if ( abstime == NULL || abstime->tv_sec < 0 || abstime->tv_nsec < 0 || abstime->tv_nsec >= MILLIARD ) return EINVAL; // Consider both scenarios, one in which the time specified by abstime has // passed // before the function could finish, and the other in which // pthread_cond_timedwait behaves basically the same as pthread_cond_wait. if ( __vm_choose( 2 ) ) { return pthread_cond_wait( cond, mutex ); } else { return ETIMEDOUT; } } /* Attributes of conditional variables */ int pthread_condattr_destroy( pthread_condattr_t * ) noexcept { /* TODO */ return 0; } int pthread_condattr_getclock( const pthread_condattr_t *, clockid_t * ) noexcept { /* TODO */ return 0; } int pthread_condattr_getpshared( const pthread_condattr_t *, int * ) noexcept { /* TODO */ return 0; } int pthread_condattr_init( pthread_condattr_t * ) noexcept { /* TODO */ return 0; } int pthread_condattr_setclock( pthread_condattr_t *, clockid_t ) noexcept { /* TODO */ return 0; } int pthread_condattr_setpshared( pthread_condattr_t *, int ) noexcept { /* TODO */ return 0; } /* Once-only execution */ /* pthread_once_t representation (extends pthread_mutex_t): -------------------------------------------------------------------------------- | *free* | should be init_routine called?: 1bit | < pthread_mutex_t (27 bits) > | -------------------------------------------------------------------------------- */ int pthread_once( pthread_once_t *once_control, void ( *init_routine )( void ) ) { if ( once_control->__mtx.__once == 0 ) return 0; pthread_mutex_lock( &once_control->__mtx ); if ( once_control->__mtx.__once ) { init_routine(); once_control->__mtx.__once = 0; } pthread_mutex_unlock( &once_control->__mtx ); return 0; } /* Thread cancellation */ int pthread_setcancelstate( int state, int *oldstate ) noexcept { __dios::FencedInterruptMask mask; if ( state & ~0x1 ) return EINVAL; _PThread &thread = getThread(); if ( oldstate ) *oldstate = thread.cancel_state; thread.cancel_state = state & 1; return 0; } int pthread_setcanceltype( int type, int *oldtype ) noexcept { __dios::FencedInterruptMask mask; if ( type & ~0x1 ) return EINVAL; _PThread &thread = getThread(); if ( oldtype ) *oldtype = thread.cancel_type; thread.cancel_type = type & 1; return 0; } int pthread_cancel( pthread_t gtid ) noexcept { __dios::FencedInterruptMask mask; _PThread *thread = nullptr; { __dios::DetectFault f( _VM_F_Memory ); thread = &getThread( gtid ); if ( f.triggered() ) return ESRCH; } if ( thread->cancel_state == PTHREAD_CANCEL_ENABLE ) thread->cancelled = true; return 0; } void pthread_testcancel( void ) noexcept { __dios::FencedInterruptMask mask; if ( _canceled() ) _cancel( mask ); } void pthread_cleanup_push( void ( *routine )( void * ), void *arg ) noexcept { __dios::FencedInterruptMask mask; assert( routine != NULL ); _PThread &thread = getThread(); CleanupHandler *handler = reinterpret_cast< CleanupHandler * >( __vm_obj_make( sizeof( CleanupHandler ) ) ); handler->routine = routine; handler->arg = arg; handler->next = thread.cleanup_handlers; thread.cleanup_handlers = handler; } void pthread_cleanup_pop( int execute ) noexcept { __dios::FencedInterruptMask mask; _PThread &thread = getThread(); CleanupHandler *handler = thread.cleanup_handlers; if ( handler != NULL ) { thread.cleanup_handlers = handler->next; if ( execute ) { handler->routine( handler->arg ); } __vm_obj_free( handler ); } } /* Readers-Writer lock */ int _rlock_adjust_count( _ReadLock *rlock, int adj ) noexcept { int count = rlock->__count; count += adj; if ( count < 0 ) return EAGAIN; rlock->__count = count; return 0; } _ReadLock *_get_rlock( pthread_rwlock_t *rwlock, _PThread &tid, _ReadLock **prev = nullptr ) noexcept { _ReadLock *rlock = rwlock->__rlocks; _ReadLock *_prev = nullptr; while ( rlock && ( rlock->__owner != &tid ) ) { _prev = rlock; rlock = rlock->__next; } if ( rlock && prev ) *prev = _prev; return rlock; } _ReadLock *_create_rlock( pthread_rwlock_t *rwlock, _PThread &tid ) noexcept { _ReadLock *rlock = reinterpret_cast< _ReadLock * >( __vm_obj_make( sizeof( _ReadLock ) ) ); rlock->__owner = &tid; rlock->__count = 1; rlock->__next = rwlock->__rlocks; rwlock->__rlocks = rlock; return rlock; } bool _rwlock_can_lock( pthread_rwlock_t *rwlock, bool writer ) noexcept { return !rwlock->__wrowner && !( writer && rwlock->__rlocks ); } int _rwlock_lock( __dios::FencedInterruptMask &mask, pthread_rwlock_t *rwlock, bool shouldwait, bool writer ) noexcept { _PThread &thr = getThread(); if ( rwlock == nullptr || !rwlock->__initialized ) { return EINVAL; // rwlock does not refer to an initialized rwlock object } if ( rwlock->__wrowner == &thr ) // thread already owns write lock return EDEADLK; _ReadLock *rlock = _get_rlock( rwlock, thr ); // existing read lock implies read lock counter having value more than zero assert( !rlock || rlock->__count ); if ( writer && rlock ) // thread already owns read lock return EDEADLK; if ( !_rwlock_can_lock( rwlock, writer ) ) { if ( !shouldwait ) return EBUSY; } wait( mask, [&] { return !_rwlock_can_lock( rwlock, writer ); } ); if ( writer ) rwlock->__wrowner = &thr; else { if ( !rlock ) rlock = _create_rlock( rwlock, thr ); else { int err = _rlock_adjust_count( rlock, 1 ); if ( err ) return err; } } return 0; } int pthread_rwlock_destroy( pthread_rwlock_t *rwlock ) noexcept { __dios::FencedInterruptMask mask; if ( rwlock == NULL ) return EINVAL; if ( rwlock->__wrowner || rwlock->__rlocks ) // rwlock is locked return EBUSY; rwlock->__initialized = 0; return 0; } int pthread_rwlock_init( pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr ) noexcept { __dios::FencedInterruptMask mask; if ( rwlock == NULL ) return EINVAL; { __dios::DetectFault f( _VM_F_Control ); if ( rwlock->__initialized && !f.triggered() ) { __dios_trace_t( "WARN: re-initializing rwlock" ); return EBUSY; } } rwlock->__processShared = attr && bool( *attr & _RWLOCK_ATTR_SHARING_MASK ); rwlock->__initialized = true; rwlock->__wrowner = nullptr; rwlock->__rlocks = nullptr; return 0; } int pthread_rwlock_rdlock( pthread_rwlock_t *rwlock ) noexcept { __dios::FencedInterruptMask mask; return _rwlock_lock( mask, rwlock, true, false ); } int pthread_rwlock_wrlock( pthread_rwlock_t *rwlock ) noexcept { __dios::FencedInterruptMask mask; return _rwlock_lock( mask, rwlock, true, true ); } int pthread_rwlock_tryrdlock( pthread_rwlock_t *rwlock ) noexcept { __dios::FencedInterruptMask mask; return _rwlock_lock( mask, rwlock, false, false ); } int pthread_rwlock_trywrlock( pthread_rwlock_t *rwlock ) noexcept { __dios::FencedInterruptMask mask; return _rwlock_lock( mask, rwlock, false, true ); } int pthread_rwlock_unlock( pthread_rwlock_t *rwlock ) noexcept { __dios::FencedInterruptMask mask; _PThread &thr = getThread(); if ( rwlock == NULL || !rwlock->__initialized ) { return EINVAL; // rwlock does not refer to an initialized rwlock object } _ReadLock *rlock, *prev; rlock = _get_rlock( rwlock, thr, &prev ); // existing read lock implies read lock counter having value more than zero assert( !rlock || rlock->__count ); if ( rwlock->__wrowner != &thr && !rlock ) { // the current thread does not own the read-write lock return EPERM; } if ( rwlock->__wrowner == &thr ) { assert( !rlock ); // release write lock rwlock->__wrowner = nullptr; } else { // release read lock _rlock_adjust_count( rlock, -1 ); if ( rlock->__count == 0 ) { // no more read locks are owned by this thread if ( prev ) prev->__next = rlock->__next; else rwlock->__rlocks = rlock->__next; __vm_obj_free( rlock ); } } return 0; } /* Readers-Write lock attributes */ /* pthread_rwlockattr_t representation: ------------------------- | *free* | sharing: 1 bit | ------------------------- */ int pthread_rwlockattr_destroy( pthread_rwlockattr_t *attr ) noexcept { __dios::FencedInterruptMask mask; if ( attr == NULL ) return EINVAL; *attr = 0; return 0; } int pthread_rwlockattr_getpshared( const pthread_rwlockattr_t *attr, int *pshared ) noexcept { __dios::FencedInterruptMask mask; if ( attr == NULL || pshared == NULL ) return EINVAL; *pshared = *attr & _RWLOCK_ATTR_SHARING_MASK; return 0; } int pthread_rwlockattr_init( pthread_rwlockattr_t *attr ) noexcept { __dios::FencedInterruptMask mask; if ( attr == NULL ) return EINVAL; *attr = PTHREAD_PROCESS_PRIVATE; return 0; } int pthread_rwlockattr_setpshared( pthread_rwlockattr_t *attr, int pshared ) noexcept { __dios::FencedInterruptMask mask; if ( attr == NULL || ( pshared & ~_RWLOCK_ATTR_SHARING_MASK ) ) return EINVAL; *attr &= ~_RWLOCK_ATTR_SHARING_MASK; *attr |= pshared; return 0; } /* Barrier */ /* pthread_barrier_t representation: { .counter: initialized: 1 bit .nthreads: the number of threads to synchronize: 15 bits } */ int pthread_barrier_destroy( pthread_barrier_t *barrier ) noexcept { __dios::FencedInterruptMask mask; if ( barrier == NULL ) return EINVAL; int r = _destroy_cond_or_barrier( barrier ); if ( r == 0 ) barrier->__nthreads = 0; return r; } int pthread_barrier_init( pthread_barrier_t *barrier, const pthread_barrierattr_t * /* TODO: barrier attributes */, unsigned count ) noexcept { __dios::FencedInterruptMask mask; if ( count == 0 || barrier == NULL ) return EINVAL; { __dios::DetectFault f( _VM_F_Control ); if ( barrier->__initialized && !f.triggered() ) { __dios_trace_t( "WARN: re-initializing barrier" ); return EBUSY; } } // Set the number of threads that must call pthread_barrier_wait() before // any of them successfully return from the call. *barrier = ( pthread_barrier_t ){ .__nthreads = ushort( count ), .__initialized = 1, .__counter = 0 }; return 0; } int pthread_barrier_wait( pthread_barrier_t *barrier ) noexcept { __dios::FencedInterruptMask mask; if ( barrier == NULL || !barrier->__initialized ) return EINVAL; int ret = 0; int counter = barrier->__counter; int release_count = barrier->__nthreads; if ( ( counter + 1 ) == release_count ) { _cond_signal< true >( barrier ); ret = PTHREAD_BARRIER_SERIAL_THREAD; } else { // WARNING: this is not immune from spurious wakeup. // As of this writing, spurious wakeup is not implemented (and probably // never will). // fall asleep _PThread &thread = getThread(); thread.setSleeping( barrier ); _cond_adjust_count( barrier, 1 ); // one more thread is blocked on this barrier // sleeping waitOrCancel( mask, [&] { return thread.sleeping == Barrier; } ); } return ret; } /* Barrier attributes */ int pthread_barrierattr_destroy( pthread_barrierattr_t * ) noexcept { /* TODO */ return 0; } int pthread_barrierattr_getpshared( const pthread_barrierattr_t *, int * ) noexcept { /* TODO */ return 0; } int pthread_barrierattr_init( pthread_barrierattr_t * ) noexcept { /* TODO */ return 0; } int pthread_barrierattr_setpshared( pthread_barrierattr_t *, int ) noexcept { /* TODO */ return 0; } /* POSIX Realtime Extension - sched.h */ int sched_get_priority_max( int ) noexcept { /* TODO */ return 0; } int sched_get_priority_min( int ) noexcept { /* TODO */ return 0; } int sched_getparam( pid_t, struct sched_param * ) noexcept { /* TODO */ return 0; } int sched_getscheduler( pid_t ) noexcept { /* TODO */ return 0; } int sched_rr_get_interval( pid_t, struct timespec * ) noexcept { /* TODO */ return 0; } int sched_setparam( pid_t, const struct sched_param * ) noexcept { /* TODO */ return 0; } int sched_setscheduler( pid_t, int, const struct sched_param * ) noexcept { /* TODO */ return 0; } int sched_yield( void ) noexcept { /* TODO */ return 0; } #pragma GCC diagnostic pop