// Copyright 2021 The Libc Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package libc // import "modernc.org/libc" import ( "runtime" "sync" "sync/atomic" "time" "unsafe" "modernc.org/libc/errno" "modernc.org/libc/pthread" "modernc.org/libc/sys/types" ctime "modernc.org/libc/time" ) var ( mutexes = map[uintptr]*mutex{} mutexesMu sync.Mutex threads = map[int32]*TLS{} threadsMu sync.Mutex threadKey pthread.Pthread_key_t threadKeyDestructors = map[pthread.Pthread_key_t][]uintptr{} // key: []destructor threadsKeysMu sync.Mutex conds = map[uintptr]*cond{} condsMu sync.Mutex ) // Thread local storage. type TLS struct { errnop uintptr pthreadData stack stackHeader ID int32 reentryGuard int32 // memgrind stackHeaderBalance int32 } var errno0 int32 // Temp errno for NewTLS func NewTLS() *TLS { return newTLS(false) } func newTLS(detached bool) *TLS { id := atomic.AddInt32(&tid, 1) t := &TLS{ID: id, errnop: uintptr(unsafe.Pointer(&errno0))} t.pthreadData.init(t, detached) if memgrind { atomic.AddInt32(&tlsBalance, 1) } t.errnop = t.Alloc(int(unsafe.Sizeof(int32(0)))) *(*int32)(unsafe.Pointer(t.errnop)) = 0 return t } // Pthread specific part of a TLS. type pthreadData struct { done chan struct{} kv map[pthread.Pthread_key_t]uintptr retVal uintptr wait chan struct{} // cond var interaction detached bool } func (d *pthreadData) init(t *TLS, detached bool) { d.detached = detached d.wait = make(chan struct{}, 1) if detached { return } d.done = make(chan struct{}) threadsMu.Lock() defer threadsMu.Unlock() threads[t.ID] = t } // int pthread_attr_init(pthread_attr_t *attr); func Xpthread_attr_init(t *TLS, pAttr uintptr) int32 { *(*pthread.Pthread_attr_t)(unsafe.Pointer(pAttr)) = pthread.Pthread_attr_t{} return 0 } // int pthread_attr_destroy(pthread_attr_t *attr); func Xpthread_attr_destroy(t *TLS, pAttr uintptr) int32 { return 0 } // int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope); func Xpthread_attr_setscope(t *TLS, pAttr uintptr, contentionScope int32) int32 { switch contentionScope { case pthread.PTHREAD_SCOPE_SYSTEM: return 0 default: panic(todo("", contentionScope)) } } // int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize); func Xpthread_attr_setstacksize(t *TLS, attr uintptr, stackSize types.Size_t) int32 { panic(todo("")) } // Go side data of pthread_cond_t. type cond struct { sync.Mutex waiters map[*TLS]struct{} } func newCond() *cond { return &cond{ waiters: map[*TLS]struct{}{}, } } func (c *cond) signal(all bool) int32 { if c == nil { return errno.EINVAL } c.Lock() defer c.Unlock() // The pthread_cond_broadcast() and pthread_cond_signal() functions shall have // no effect if there are no threads currently blocked on cond. for tls := range c.waiters { tls.wait <- struct{}{} delete(c.waiters, tls) if !all { break } } return 0 } // The pthread_cond_init() function shall initialize the condition variable // referenced by cond with attributes referenced by attr. If attr is NULL, the // default condition variable attributes shall be used; the effect is the same // as passing the address of a default condition variable attributes object. // Upon successful initialization, the state of the condition variable shall // become initialized. // // If successful, the pthread_cond_destroy() and pthread_cond_init() functions // shall return zero; otherwise, an error number shall be returned to indicate // the error. // // int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); func Xpthread_cond_init(t *TLS, pCond, pAttr uintptr) int32 { if pCond == 0 { return errno.EINVAL } if pAttr != 0 { panic(todo("%#x %#x", pCond, pAttr)) } condsMu.Lock() defer condsMu.Unlock() conds[pCond] = newCond() return 0 } // int pthread_cond_destroy(pthread_cond_t *cond); func Xpthread_cond_destroy(t *TLS, pCond uintptr) int32 { if pCond == 0 { return errno.EINVAL } condsMu.Lock() defer condsMu.Unlock() cond := conds[pCond] if cond == nil { return errno.EINVAL } cond.Lock() defer cond.Unlock() if len(cond.waiters) != 0 { return errno.EBUSY } delete(conds, pCond) return 0 } // int pthread_cond_signal(pthread_cond_t *cond); func Xpthread_cond_signal(t *TLS, pCond uintptr) int32 { return condSignal(pCond, false) } // int pthread_cond_broadcast(pthread_cond_t *cond); func Xpthread_cond_broadcast(t *TLS, pCond uintptr) int32 { return condSignal(pCond, true) } func condSignal(pCond uintptr, all bool) int32 { if pCond == 0 { return errno.EINVAL } condsMu.Lock() cond := conds[pCond] condsMu.Unlock() return cond.signal(all) } // int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); func Xpthread_cond_wait(t *TLS, pCond, pMutex uintptr) int32 { if pCond == 0 { return errno.EINVAL } condsMu.Lock() cond := conds[pCond] if cond == nil { // static initialized condition variables are valid cond = newCond() conds[pCond] = cond } cond.Lock() cond.waiters[t] = struct{}{} cond.Unlock() condsMu.Unlock() mutexesMu.Lock() mu := mutexes[pMutex] mutexesMu.Unlock() mu.Unlock() <-t.wait mu.Lock() return 0 } // int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime); func Xpthread_cond_timedwait(t *TLS, pCond, pMutex, pAbsTime uintptr) int32 { if pCond == 0 { return errno.EINVAL } condsMu.Lock() cond := conds[pCond] if cond == nil { // static initialized condition variables are valid cond = newCond() conds[pCond] = cond } cond.Lock() cond.waiters[t] = struct{}{} cond.Unlock() condsMu.Unlock() mutexesMu.Lock() mu := mutexes[pMutex] mutexesMu.Unlock() deadlineSecs := (*ctime.Timespec)(unsafe.Pointer(pAbsTime)).Ftv_sec deadlineNsecs := (*ctime.Timespec)(unsafe.Pointer(pAbsTime)).Ftv_nsec deadline := time.Unix(int64(deadlineSecs), int64(deadlineNsecs)) d := deadline.Sub(time.Now()) switch { case d <= 0: return errno.ETIMEDOUT default: to := time.After(d) mu.Unlock() defer mu.Lock() select { case <-t.wait: return 0 case <-to: cond.Lock() defer cond.Unlock() delete(cond.waiters, t) return errno.ETIMEDOUT } } } // Go side data of pthread_mutex_t type mutex struct { sync.Mutex typ int // PTHREAD_MUTEX_NORMAL, ... wait sync.Mutex id int32 // owner's t.ID cnt int32 robust bool } func newMutex(typ int) *mutex { return &mutex{ typ: typ, } } func (m *mutex) lock(id int32) int32 { if m.robust { panic(todo("")) } // If successful, the pthread_mutex_lock() and pthread_mutex_unlock() functions // shall return zero; otherwise, an error number shall be returned to indicate // the error. switch m.typ { case pthread.PTHREAD_MUTEX_NORMAL: // If the mutex type is PTHREAD_MUTEX_NORMAL, deadlock detection shall not be // provided. Attempting to relock the mutex causes deadlock. If a thread // attempts to unlock a mutex that it has not locked or a mutex which is // unlocked, undefined behavior results. m.Lock() m.id = id return 0 case pthread.PTHREAD_MUTEX_RECURSIVE: for { m.Lock() switch m.id { case 0: m.cnt = 1 m.id = id m.wait.Lock() m.Unlock() return 0 case id: m.cnt++ m.Unlock() return 0 } m.Unlock() m.wait.Lock() m.wait.Unlock() } default: panic(todo("", m.typ)) } } func (m *mutex) tryLock(id int32) int32 { if m.robust { panic(todo("")) } switch m.typ { case pthread.PTHREAD_MUTEX_NORMAL: return errno.EBUSY case pthread.PTHREAD_MUTEX_RECURSIVE: m.Lock() switch m.id { case 0: m.cnt = 1 m.id = id m.wait.Lock() m.Unlock() return 0 case id: m.cnt++ m.Unlock() return 0 } m.Unlock() return errno.EBUSY default: panic(todo("", m.typ)) } } func (m *mutex) unlock() int32 { if m.robust { panic(todo("")) } // If successful, the pthread_mutex_lock() and pthread_mutex_unlock() functions // shall return zero; otherwise, an error number shall be returned to indicate // the error. switch m.typ { case pthread.PTHREAD_MUTEX_NORMAL: // If the mutex type is PTHREAD_MUTEX_NORMAL, deadlock detection shall not be // provided. Attempting to relock the mutex causes deadlock. If a thread // attempts to unlock a mutex that it has not locked or a mutex which is // unlocked, undefined behavior results. m.id = 0 m.Unlock() return 0 case pthread.PTHREAD_MUTEX_RECURSIVE: m.Lock() m.cnt-- if m.cnt == 0 { m.id = 0 m.wait.Unlock() } m.Unlock() return 0 default: panic(todo("", m.typ)) } } // The pthread_mutex_init() function shall initialize the mutex referenced by // mutex with attributes specified by attr. If attr is NULL, the default mutex // attributes are used; the effect shall be the same as passing the address of // a default mutex attributes object. Upon successful initialization, the state // of the mutex becomes initialized and unlocked. // // If successful, the pthread_mutex_destroy() and pthread_mutex_init() // functions shall return zero; otherwise, an error number shall be returned to // indicate the error. // // int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); func Xpthread_mutex_init(t *TLS, pMutex, pAttr uintptr) int32 { typ := pthread.PTHREAD_MUTEX_DEFAULT if pAttr != 0 { typ = int(X__ccgo_pthreadMutexattrGettype(t, pAttr)) } mutexesMu.Lock() defer mutexesMu.Unlock() mutexes[pMutex] = newMutex(typ) return 0 } // int pthread_mutex_destroy(pthread_mutex_t *mutex); func Xpthread_mutex_destroy(t *TLS, pMutex uintptr) int32 { mutexesMu.Lock() defer mutexesMu.Unlock() delete(mutexes, pMutex) return 0 } // int pthread_mutex_lock(pthread_mutex_t *mutex); func Xpthread_mutex_lock(t *TLS, pMutex uintptr) int32 { mutexesMu.Lock() mu := mutexes[pMutex] if mu == nil { // static initialized mutexes are valid mu = newMutex(int(X__ccgo_getMutexType(t, pMutex))) mutexes[pMutex] = mu } mutexesMu.Unlock() return mu.lock(t.ID) } // int pthread_mutex_trylock(pthread_mutex_t *mutex); func Xpthread_mutex_trylock(t *TLS, pMutex uintptr) int32 { mutexesMu.Lock() mu := mutexes[pMutex] if mu == nil { // static initialized mutexes are valid mu = newMutex(int(X__ccgo_getMutexType(t, pMutex))) mutexes[pMutex] = mu } mutexesMu.Unlock() return mu.tryLock(t.ID) } // int pthread_mutex_unlock(pthread_mutex_t *mutex); func Xpthread_mutex_unlock(t *TLS, pMutex uintptr) int32 { mutexesMu.Lock() defer mutexesMu.Unlock() return mutexes[pMutex].unlock() } // int pthread_key_create(pthread_key_t *key, void (*destructor)(void*)); func Xpthread_key_create(t *TLS, pKey, destructor uintptr) int32 { threadsKeysMu.Lock() defer threadsKeysMu.Unlock() threadKey++ r := threadKey if destructor != 0 { threadKeyDestructors[r] = append(threadKeyDestructors[r], destructor) } *(*pthread.Pthread_key_t)(unsafe.Pointer(pKey)) = pthread.Pthread_key_t(r) return 0 } // int pthread_key_delete(pthread_key_t key); func Xpthread_key_delete(t *TLS, key pthread.Pthread_key_t) int32 { if _, ok := t.kv[key]; ok { delete(t.kv, key) return 0 } panic(todo("")) } // void *pthread_getspecific(pthread_key_t key); func Xpthread_getspecific(t *TLS, key pthread.Pthread_key_t) uintptr { return t.kv[key] } // int pthread_setspecific(pthread_key_t key, const void *value); func Xpthread_setspecific(t *TLS, key pthread.Pthread_key_t, value uintptr) int32 { if t.kv == nil { t.kv = map[pthread.Pthread_key_t]uintptr{} } t.kv[key] = value return 0 } // int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); func Xpthread_create(t *TLS, pThread, pAttr, startRoutine, arg uintptr) int32 { fn := (*struct { f func(*TLS, uintptr) uintptr })(unsafe.Pointer(&struct{ uintptr }{startRoutine})).f detached := pAttr != 0 && X__ccgo_pthreadAttrGetDetachState(t, pAttr) == pthread.PTHREAD_CREATE_DETACHED tls := newTLS(detached) *(*pthread.Pthread_t)(unsafe.Pointer(pThread)) = pthread.Pthread_t(tls.ID) go func() { Xpthread_exit(tls, fn(tls, arg)) }() return 0 } // int pthread_detach(pthread_t thread); func Xpthread_detach(t *TLS, thread pthread.Pthread_t) int32 { threadsMu.Lock() threads[int32(thread)].detached = true threadsMu.Unlock() return 0 } // int pthread_equal(pthread_t t1, pthread_t t2); func Xpthread_equal(t *TLS, t1, t2 pthread.Pthread_t) int32 { panic(todo("")) } // void pthread_exit(void *value_ptr); func Xpthread_exit(t *TLS, value uintptr) { t.retVal = 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. for k, v := range t.kv { if v == 0 { continue } threadsKeysMu.Lock() destructors := threadKeyDestructors[k] threadsKeysMu.Unlock() for _, destructor := range destructors { delete(t.kv, k) panic(todo("%#x", destructor)) //TODO call destructor(v) } } switch { case t.detached: threadsMu.Lock() delete(threads, t.ID) threadsMu.Unlock() default: close(t.done) } runtime.Goexit() } // int pthread_join(pthread_t thread, void **value_ptr); func Xpthread_join(t *TLS, thread pthread.Pthread_t, pValue uintptr) int32 { threadsMu.Lock() tls := threads[int32(thread)] delete(threads, int32(thread)) threadsMu.Unlock() <-tls.done if pValue != 0 { *(*uintptr)(unsafe.Pointer(pValue)) = tls.retVal } return 0 } // pthread_t pthread_self(void); func Xpthread_self(t *TLS) pthread.Pthread_t { return pthread.Pthread_t(t.ID) }