Skip to content

Commit

Permalink
Atomic value setters. (JetBrains#1902)
Browse files Browse the repository at this point in the history
  • Loading branch information
olonho authored Aug 18, 2018
1 parent a8951f9 commit bed09e1
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 0 deletions.
22 changes: 22 additions & 0 deletions backend.native/tests/runtime/workers/atomic0.kt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,26 @@ fun test4() {
}
}

fun test5() {
assertFailsWith<InvalidMutabilityException> {
AtomicReference<Data>().set(Data(2))
}
val ref = AtomicReference<Data>()
val value = Data(3).freeze()
assertEquals(null, ref.get())
ref.set(value)
assertEquals(3, ref.get()!!.value)
}

fun test6() {
val int = AtomicInt()
int.set(239)
assertEquals(239, int.get())
val long = AtomicLong()
long.set(239L)
assertEquals(239L, long.get())
}

@Test fun runTest() {
val COUNT = 20
val workers = Array(COUNT, { _ -> startWorker()})
Expand All @@ -86,6 +106,8 @@ fun test4() {
test2(workers)
test3(workers)
test4()
test5()
test6()

workers.forEach {
it.requestTermination().consume { _ -> }
Expand Down
33 changes: 33 additions & 0 deletions runtime/src/main/cpp/Atomic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ struct AtomicReferenceLayout {
KInt lock_;
};

template <typename T> void setImpl(KRef thiz, T value) {
volatile T* location = reinterpret_cast<volatile T*>(thiz + 1);
atomicSet(location, value);
}

template <typename T> T addAndGetImpl(KRef thiz, T delta) {
volatile T* location = reinterpret_cast<volatile T*>(thiz + 1);
return atomicAdd(location, delta);
Expand All @@ -53,6 +58,10 @@ KInt Kotlin_AtomicInt_compareAndSwap(KRef thiz, KInt expectedValue, KInt newValu
return compareAndSwapImpl(thiz, expectedValue, newValue);
}

void Kotlin_AtomicInt_set(KRef thiz, KInt newValue) {
setImpl(thiz, newValue);
}

KLong Kotlin_AtomicLong_addAndGet(KRef thiz, KLong delta) {
return addAndGetImpl(thiz, delta);
}
Expand All @@ -75,10 +84,28 @@ KLong Kotlin_AtomicLong_compareAndSwap(KRef thiz, KLong expectedValue, KLong new
#endif
}

void Kotlin_AtomicLong_set(KRef thiz, KLong newValue) {
#ifdef __mips
// Potentially huge performance penalty, but correct.
// TODO: reconsider, once target MIPS can do proper 64-bit atomic store.
static int lock = 0;
while (compareAndSwap(&lock, 0, 1) != 0);
KLong* address = reinterpret_cast<KLong*>(thiz + 1);
*address = newValue;
compareAndSwap(&lock, 1, 0);
#else
setImpl(thiz, newValue);
#endif
}

KNativePtr Kotlin_AtomicNativePtr_compareAndSwap(KRef thiz, KNativePtr expectedValue, KNativePtr newValue) {
return compareAndSwapImpl(thiz, expectedValue, newValue);
}

void Kotlin_AtomicNativePtr_set(KRef thiz, KNativePtr newValue) {
setImpl(thiz, newValue);
}

void Kotlin_AtomicReference_checkIfFrozen(KRef value) {
if (value != nullptr && !value->container()->permanentOrFrozen()) {
ThrowInvalidMutabilityException(value);
Expand All @@ -92,6 +119,12 @@ OBJ_GETTER(Kotlin_AtomicReference_compareAndSwap, KRef thiz, KRef expectedValue,
RETURN_RESULT_OF(SwapRefLocked, &ref->value_, expectedValue, newValue, &ref->lock_);
}

void Kotlin_AtomicReference_set(KRef thiz, KRef newValue) {
Kotlin_AtomicReference_checkIfFrozen(newValue);
AtomicReferenceLayout* ref = asAtomicReference(thiz);
SetRefLocked(&ref->value_, newValue, &ref->lock_);
}

OBJ_GETTER(Kotlin_AtomicReference_get, KRef thiz) {
// Here we must take a lock to prevent race when value, while taken here, is CASed and immediately
// destroyed by an another thread. AtomicReference no longer holds such an object, so if we got
Expand Down
8 changes: 8 additions & 0 deletions runtime/src/main/cpp/Atomic.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,13 @@ ALWAYS_INLINE inline T compareAndSwap(volatile T* where, T expectedValue, T newV
#endif
}

template <typename T>
ALWAYS_INLINE inline void atomicSet(volatile T* where, T what) {
#ifndef KONAN_NO_THREADS
__atomic_store(where, &what, __ATOMIC_SEQ_CST);
#else
*where = what;
#endif
}

#endif // RUNTIME_ATOMIC_H
10 changes: 10 additions & 0 deletions runtime/src/main/cpp/Memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1743,6 +1743,16 @@ OBJ_GETTER(SwapRefLocked,
return oldValue;
}

void SetRefLocked(ObjHeader** location, ObjHeader* newValue, int32_t* spinlock) {
lock(spinlock);
ObjHeader* oldValue = *location;
// We do not use UpdateRef() here to avoid having ReleaseRef() on old value under the lock.
SetRef(location, newValue);
unlock(spinlock);
if (oldValue != nullptr)
ReleaseRef(oldValue);
}

OBJ_GETTER(ReadRefLocked, ObjHeader** location, int32_t* spinlock) {
lock(spinlock);
ObjHeader* value = *location;
Expand Down
2 changes: 2 additions & 0 deletions runtime/src/main/cpp/Memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,8 @@ void UpdateReturnRef(ObjHeader** returnSlot, const ObjHeader* object) RUNTIME_NO
// Compares and swaps reference with taken lock.
OBJ_GETTER(SwapRefLocked,
ObjHeader** location, ObjHeader* expectedValue, ObjHeader* newValue, int32_t* spinlock) RUNTIME_NOTHROW;
// Sets reference with taken lock.
void SetRefLocked(ObjHeader** location, ObjHeader* newValue, int32_t* spinlock) RUNTIME_NOTHROW;
// Reads reference with taken lock.
OBJ_GETTER(ReadRefLocked, ObjHeader** location, int32_t* spinlock) RUNTIME_NOTHROW;
// Optimization: release all references in range.
Expand Down
20 changes: 20 additions & 0 deletions runtime/src/main/kotlin/kotlin/native/worker/Atomics.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ class AtomicInt(private var value: Int = 0) {
@SymbolName("Kotlin_AtomicInt_compareAndSwap")
external fun compareAndSwap(expected: Int, new: Int): Int

/**
* Sets the new atomic value.
*/
@SymbolName("Kotlin_AtomicInt_set")
external fun set(new: Int): Unit

/**
* Increments value by one.
*/
Expand Down Expand Up @@ -79,6 +85,12 @@ class AtomicLong(private var value: Long = 0) {
@SymbolName("Kotlin_AtomicLong_compareAndSwap")
external fun compareAndSwap(expected: Long, new: Long): Long

/**
* Sets the new atomic value.
*/
@SymbolName("Kotlin_AtomicLong_set")
external fun set(new: Long): Unit

/**
* Increments value by one.
*/
Expand Down Expand Up @@ -146,6 +158,14 @@ class AtomicReference<T>(private var value: T? = null) {
@SymbolName("Kotlin_AtomicReference_compareAndSwap")
external public fun compareAndSwap(expected: T?, new: T?): T?

/**
* Sets the value to [new] value
* If [new] value is not null, it must be frozen or permanent object, otherwise an
* @InvalidMutabilityException is thrown.
*/
@SymbolName("Kotlin_AtomicReference_set")
external public fun set(new: T?): Unit

/**
* Returns the current value.
*/
Expand Down

0 comments on commit bed09e1

Please sign in to comment.