[K/N] Fix possible data races found by thread sanitizer
This commit is contained in:
@@ -12,33 +12,6 @@ ALWAYS_INLINE inline T atomicAdd(volatile T* where, T what) {
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ALWAYS_INLINE inline T compareAndSwap(volatile T* where, T expectedValue, T newValue) {
|
||||
#ifndef KONAN_NO_THREADS
|
||||
return __sync_val_compare_and_swap(where, expectedValue, newValue);
|
||||
#else
|
||||
T oldValue = *where;
|
||||
if (oldValue == expectedValue) {
|
||||
*where = newValue;
|
||||
}
|
||||
return oldValue;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ALWAYS_INLINE inline bool compareAndSet(volatile T* where, T expectedValue, T newValue) {
|
||||
#ifndef KONAN_NO_THREADS
|
||||
return __sync_bool_compare_and_swap(where, expectedValue, newValue);
|
||||
#else
|
||||
T oldValue = *where;
|
||||
if (oldValue == expectedValue) {
|
||||
*where = newValue;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
|
||||
#if (KONAN_ANDROID || KONAN_IOS || KONAN_WATCHOS || KONAN_LINUX) && (KONAN_ARM32 || KONAN_X86 || KONAN_MIPS32 || KONAN_MIPSEL32)
|
||||
@@ -49,26 +22,79 @@ ALWAYS_INLINE inline bool compareAndSet(volatile T* where, T expectedValue, T ne
|
||||
#pragma clang diagnostic ignored "-Watomic-alignment"
|
||||
#endif
|
||||
|
||||
// as if (std::atomic<T> where).compare_exchange_strong(expectedValue, newValue)
|
||||
template <typename T>
|
||||
ALWAYS_INLINE inline bool compareExchange(volatile T& where, T &expectedValue, T newValue) {
|
||||
#ifndef KONAN_NO_THREADS
|
||||
#ifdef KONAN_NO_64BIT_ATOMIC
|
||||
static_assert(sizeof(T) <= 4);
|
||||
#endif
|
||||
return __atomic_compare_exchange_n(&where, &expectedValue, newValue, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
|
||||
#else
|
||||
T oldValue = where;
|
||||
if (oldValue == expectedValue) {
|
||||
where = newValue;
|
||||
return true;
|
||||
}
|
||||
expectedValue = oldValue;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ALWAYS_INLINE inline T compareAndSwap(volatile T* where, T expectedValue, T newValue) {
|
||||
compareExchange(*where, expectedValue, newValue);
|
||||
return expectedValue;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ALWAYS_INLINE inline bool compareAndSet(volatile T* where, T expectedValue, T newValue) {
|
||||
return compareExchange(*where, expectedValue, newValue);
|
||||
}
|
||||
|
||||
|
||||
template <int model = __ATOMIC_SEQ_CST, typename T>
|
||||
ALWAYS_INLINE inline void atomicSet(volatile T* where, T what) {
|
||||
#ifndef KONAN_NO_THREADS
|
||||
__atomic_store(where, &what, __ATOMIC_SEQ_CST);
|
||||
#ifdef KONAN_NO_64BIT_ATOMIC
|
||||
static_assert(sizeof(T) <= 4);
|
||||
#endif
|
||||
__atomic_store(where, &what, model);
|
||||
#else
|
||||
*where = what;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ALWAYS_INLINE inline T atomicGet(volatile T* where) {
|
||||
ALWAYS_INLINE inline void atomicSetRelease(volatile T* where, T what) {
|
||||
return atomicSet<__ATOMIC_RELEASE>(where, what);
|
||||
}
|
||||
|
||||
|
||||
template <int model = __ATOMIC_SEQ_CST, typename T>
|
||||
ALWAYS_INLINE inline T atomicGet(volatile const T* where) {
|
||||
#ifndef KONAN_NO_THREADS
|
||||
#ifdef KONAN_NO_64BIT_ATOMIC
|
||||
static_assert(sizeof(T) <= 4);
|
||||
#endif
|
||||
T what;
|
||||
__atomic_load(where, &what, __ATOMIC_SEQ_CST);
|
||||
__atomic_load(where, &what, model);
|
||||
return what;
|
||||
#else
|
||||
return *where;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ALWAYS_INLINE inline T atomicGetAcquire(volatile const T* where) {
|
||||
return atomicGet<__ATOMIC_ACQUIRE>(where);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ALWAYS_INLINE inline T atomicGetRelaxed(volatile const T* where) {
|
||||
return atomicGet<__ATOMIC_RELAXED>(where);
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
static ALWAYS_INLINE inline void synchronize() {
|
||||
|
||||
@@ -59,35 +59,38 @@ struct ObjHeader {
|
||||
}
|
||||
}
|
||||
|
||||
TypeInfo* typeInfoOrMetaRelaxed() const { return atomicGetRelaxed(&typeInfoOrMeta_);}
|
||||
TypeInfo* typeInfoOrMetaAcquire() const { return atomicGetAcquire(&typeInfoOrMeta_);}
|
||||
|
||||
const TypeInfo* type_info() const {
|
||||
return clearPointerBits(typeInfoOrMeta_, OBJECT_TAG_MASK)->typeInfo_;
|
||||
return clearPointerBits(typeInfoOrMetaAcquire(), OBJECT_TAG_MASK)->typeInfo_;
|
||||
}
|
||||
|
||||
bool has_meta_object() const {
|
||||
return AsMetaObject(typeInfoOrMeta_) != nullptr;
|
||||
return meta_object_or_null() != nullptr;
|
||||
}
|
||||
|
||||
MetaObjHeader* meta_object() {
|
||||
if (auto* metaObject = AsMetaObject(typeInfoOrMeta_)) {
|
||||
if (auto* metaObject = AsMetaObject(typeInfoOrMetaAcquire())) {
|
||||
return metaObject;
|
||||
}
|
||||
return createMetaObject(this);
|
||||
}
|
||||
|
||||
MetaObjHeader* meta_object_or_null() const noexcept { return AsMetaObject(typeInfoOrMeta_); }
|
||||
MetaObjHeader* meta_object_or_null() const noexcept { return AsMetaObject(typeInfoOrMetaAcquire()); }
|
||||
|
||||
ALWAYS_INLINE ObjHeader* GetWeakCounter();
|
||||
ALWAYS_INLINE ObjHeader* GetOrSetWeakCounter(ObjHeader* counter);
|
||||
|
||||
|
||||
#ifdef KONAN_OBJC_INTEROP
|
||||
ALWAYS_INLINE void* GetAssociatedObject();
|
||||
ALWAYS_INLINE void** GetAssociatedObjectLocation();
|
||||
ALWAYS_INLINE void* GetAssociatedObject() const;
|
||||
ALWAYS_INLINE void SetAssociatedObject(void* obj);
|
||||
ALWAYS_INLINE void* CasAssociatedObject(void* expectedObj, void* obj);
|
||||
#endif
|
||||
|
||||
inline bool local() const {
|
||||
unsigned bits = getPointerBits(typeInfoOrMeta_, OBJECT_TAG_MASK);
|
||||
unsigned bits = getPointerBits(typeInfoOrMetaRelaxed(), OBJECT_TAG_MASK);
|
||||
return (bits & (OBJECT_TAG_PERMANENT_CONTAINER | OBJECT_TAG_NONTRIVIAL_CONTAINER)) ==
|
||||
(OBJECT_TAG_PERMANENT_CONTAINER | OBJECT_TAG_NONTRIVIAL_CONTAINER);
|
||||
}
|
||||
@@ -98,10 +101,10 @@ struct ObjHeader {
|
||||
const ArrayHeader* array() const { return reinterpret_cast<const ArrayHeader*>(this); }
|
||||
|
||||
inline bool permanent() const {
|
||||
return hasPointerBits(typeInfoOrMeta_, OBJECT_TAG_PERMANENT_CONTAINER);
|
||||
return hasPointerBits(typeInfoOrMetaRelaxed(), OBJECT_TAG_PERMANENT_CONTAINER);
|
||||
}
|
||||
|
||||
inline bool heap() const { return getPointerBits(typeInfoOrMeta_, OBJECT_TAG_MASK) == 0; }
|
||||
inline bool heap() const { return getPointerBits(typeInfoOrMetaRelaxed(), OBJECT_TAG_MASK) == 0; }
|
||||
|
||||
static MetaObjHeader* createMetaObject(ObjHeader* object);
|
||||
static void destroyMetaObject(ObjHeader* object);
|
||||
|
||||
@@ -23,8 +23,7 @@ inline static void SetAssociatedObject(ObjHeader* obj, id value) {
|
||||
}
|
||||
|
||||
inline static id AtomicCompareAndSwapAssociatedObject(ObjHeader* obj, id expectedValue, id newValue) {
|
||||
id* location = reinterpret_cast<id*>(obj->GetAssociatedObjectLocation());
|
||||
return __sync_val_compare_and_swap(location, expectedValue, newValue);
|
||||
return static_cast<id>(obj->CasAssociatedObject(expectedValue, newValue));
|
||||
}
|
||||
|
||||
inline static OBJ_GETTER(AllocInstanceWithAssociatedObject, const TypeInfo* typeInfo, id associatedObject) {
|
||||
|
||||
@@ -472,25 +472,27 @@ RUNTIME_NOTHROW void Kotlin_initRuntimeIfNeededFromKotlin() {
|
||||
}
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
static void CallInitGlobalAwaitInitialized(int *state) {
|
||||
int localState;
|
||||
// Switch to the native state to avoid dead-locks.
|
||||
{
|
||||
kotlin::ThreadStateGuard guard(kotlin::ThreadState::kNative);
|
||||
do {
|
||||
localState = atomicGetAcquire(state);
|
||||
} while (localState != FILE_INITIALIZED && localState != FILE_FAILED_TO_INITIALIZE);
|
||||
}
|
||||
if (localState == FILE_FAILED_TO_INITIALIZE) ThrowFileFailedToInitializeException();
|
||||
}
|
||||
|
||||
namespace {
|
||||
void callInitGlobalPossiblyLockImpl(int volatile* state, void (*init)()) {
|
||||
int localState = *state;
|
||||
NO_INLINE void CallInitGlobalPossiblyLock(int* state, void (*init)()) {
|
||||
int localState = atomicGetAcquire(state);
|
||||
if (localState == FILE_INITIALIZED) return;
|
||||
if (localState == FILE_FAILED_TO_INITIALIZE)
|
||||
ThrowFileFailedToInitializeException();
|
||||
int threadId = konan::currentThreadId();
|
||||
if ((localState & 3) == FILE_BEING_INITIALIZED) {
|
||||
if ((localState & ~3) != (threadId << 2)) {
|
||||
// Switch to the native state to avoid dead-locks.
|
||||
kotlin::ThreadStateGuard guard(kotlin::ThreadState::kNative);
|
||||
do {
|
||||
localState = *state;
|
||||
if (localState == FILE_FAILED_TO_INITIALIZE)
|
||||
// Call of a Kotlin function.
|
||||
kotlin::CallWithThreadState<kotlin::ThreadState::kRunnable>(ThrowFileFailedToInitializeException);
|
||||
} while (localState != FILE_INITIALIZED);
|
||||
CallInitGlobalAwaitInitialized(state);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -502,33 +504,15 @@ void callInitGlobalPossiblyLockImpl(int volatile* state, void (*init)()) {
|
||||
try {
|
||||
init();
|
||||
} catch (...) {
|
||||
*state = FILE_FAILED_TO_INITIALIZE;
|
||||
atomicSetRelease(state, FILE_FAILED_TO_INITIALIZE);
|
||||
throw;
|
||||
}
|
||||
#endif
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
*state = FILE_INITIALIZED;
|
||||
atomicSetRelease(state, FILE_INITIALIZED);
|
||||
} else {
|
||||
// Switch to the native state to avoid dead-locks.
|
||||
kotlin::ThreadStateGuard guard(kotlin::ThreadState::kNative);
|
||||
do {
|
||||
localState = *state;
|
||||
if (localState == FILE_FAILED_TO_INITIALIZE)
|
||||
// Call of a Kotlin function.
|
||||
kotlin::CallWithThreadState<kotlin::ThreadState::kRunnable>(ThrowFileFailedToInitializeException);
|
||||
} while (localState != FILE_INITIALIZED);
|
||||
CallInitGlobalAwaitInitialized(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
NO_INLINE void CallInitGlobalPossiblyLock(int volatile* state, void (*init)()) {
|
||||
callInitGlobalPossiblyLockImpl(state, init);
|
||||
// Ensure proper synchronization around reading/writing of [state] (release barrier defined in callInitGlobalPossiblyLockImpl),
|
||||
// also there is an acquire load of [state] in IrToBitcode.kt::evaluateFileGlobalInitializerCall.
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void CallInitThreadLocal(int volatile* globalState, int* localState, void (*init)()) {
|
||||
if (*localState == FILE_FAILED_TO_INITIALIZE || (globalState != nullptr && *globalState == FILE_FAILED_TO_INITIALIZE))
|
||||
|
||||
@@ -38,7 +38,7 @@ void Kotlin_shutdownRuntime();
|
||||
// Appends given node to an initializer list.
|
||||
void AppendToInitializersTail(struct InitNode*);
|
||||
|
||||
void CallInitGlobalPossiblyLock(int volatile* state, void (*init)());
|
||||
void CallInitGlobalPossiblyLock(int* state, void (*init)());
|
||||
void CallInitThreadLocal(int volatile* globalState, int* localState, void (*init)());
|
||||
|
||||
bool Kotlin_memoryLeakCheckerEnabled();
|
||||
|
||||
@@ -324,8 +324,11 @@ class Future {
|
||||
|
||||
void cancelUnlocked(MemoryState* memoryState);
|
||||
|
||||
KInt stateUnlocked() const {
|
||||
Locker locker(&lock_);
|
||||
return state_;
|
||||
}
|
||||
// Those are called with the lock taken.
|
||||
KInt state() const { return state_; }
|
||||
KInt id() const { return id_; }
|
||||
|
||||
private:
|
||||
@@ -336,8 +339,8 @@ class Future {
|
||||
// Stable pointer with future's result.
|
||||
KNativePtr result_;
|
||||
// Lock and condition for waiting on the future.
|
||||
pthread_mutex_t lock_;
|
||||
pthread_cond_t cond_;
|
||||
mutable pthread_mutex_t lock_;
|
||||
mutable pthread_cond_t cond_;
|
||||
};
|
||||
|
||||
class State {
|
||||
@@ -488,7 +491,7 @@ class State {
|
||||
Locker locker(&lock_);
|
||||
auto it = futures_.find(id);
|
||||
if (it == futures_.end()) return INVALID;
|
||||
return it->second->state();
|
||||
return it->second->stateUnlocked();
|
||||
}
|
||||
|
||||
OBJ_GETTER(consumeFutureUnlocked, KInt id) {
|
||||
|
||||
Reference in New Issue
Block a user