Skip to content
This repository has been archived by the owner on Aug 10, 2021. It is now read-only.

Commit

Permalink
Atomic values support. (#1695)
Browse files Browse the repository at this point in the history
  • Loading branch information
olonho authored Jun 18, 2018
1 parent 5e6e23d commit 94fb9c0
Show file tree
Hide file tree
Showing 14 changed files with 406 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,17 @@ internal fun IrSimpleFunction.resolveFakeOverride(): IrSimpleFunction {
}

private val intrinsicAnnotation = FqName("konan.internal.Intrinsic")
private val immutableAnnotation = FqName("konan.internal.Immutable")
private val frozenAnnotation = FqName("konan.internal.Frozen")

// TODO: don't forget to remove descriptor access here.
internal val FunctionDescriptor.isIntrinsic: Boolean
get() = this.descriptor.annotations.hasAnnotation(intrinsicAnnotation)

internal val org.jetbrains.kotlin.descriptors.DeclarationDescriptor.isImmutable: Boolean
get() = this.annotations.hasAnnotation(immutableAnnotation)
internal val org.jetbrains.kotlin.descriptors.DeclarationDescriptor.isFrozen: Boolean
get() = this.annotations.hasAnnotation(frozenAnnotation)

internal val DeclarationDescriptor.isImmutable: Boolean
get() = this.descriptor.isImmutable
internal val DeclarationDescriptor.isFrozen: Boolean
get() = this.descriptor.isFrozen

private val intrinsicTypes = setOf(
"kotlin.Boolean", "kotlin.Char",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
Expand Down Expand Up @@ -1413,7 +1412,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
private fun needMutationCheck(descriptor: org.jetbrains.kotlin.descriptors.DeclarationDescriptor): Boolean {
// For now we omit mutation checks on immutable types, as this allows initialization in constructor
// and it is assumed that API doesn't allow to change them.
return !descriptor.isImmutable
return !descriptor.isFrozen
}

private fun evaluateSetField(value: IrSetField): LLVMValueRef {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import llvm.*
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.descriptors.*
import org.jetbrains.kotlin.backend.konan.irasdescriptors.*
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.declarations.IrField
import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.name.FqName
Expand All @@ -31,7 +30,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {

private fun flagsFromClass(classDescriptor: ClassDescriptor): Int {
var result = 0
if (classDescriptor.isImmutable)
if (classDescriptor.isFrozen)
result = result or 1 /* TF_IMMUTABLE */
return result
}
Expand Down
6 changes: 6 additions & 0 deletions backend.native/tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,12 @@ task freeze2(type: RunKonanTest) {
source = "runtime/workers/freeze2.kt"
}

task atomic0(type: RunKonanTest) {
disabled = (project.testTarget == 'wasm32') // Workers need pthreads.
goldValue = "35\n" + "20\n"
source = "runtime/workers/atomic0.kt"
}

task enumIdentity(type: RunKonanTest) {
disabled = (project.testTarget == 'wasm32') // Workers need pthreads.
goldValue = "true\n"
Expand Down
83 changes: 83 additions & 0 deletions backend.native/tests/runtime/workers/atomic0.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package runtime.workers.atomic0

import kotlin.test.*

import konan.worker.*

fun test1(workers: Array<Worker>) {
val atomic = AtomicInt(15)
val futures = Array(workers.size, { workerIndex ->
workers[workerIndex].schedule(TransferMode.CHECKED, { atomic }) {
input -> input.increment()
}
})
futures.forEach {
it.result()
}
println(atomic.get())
}

fun test2(workers: Array<Worker>) {
val atomic = AtomicInt(0)
val counter = AtomicInt(0)
val futures = Array(workers.size, { workerIndex ->
workers[workerIndex].schedule(TransferMode.CHECKED, { Triple(atomic, workerIndex, counter) }) {
(place, index, result) ->
while (place.compareAndSwap(index, index + 1) != index) {}
result.increment() == index + 1
}
})
futures.forEach {
assertEquals(it.result(), true)
}
println(counter.get())
}

data class Data(val value: Int)

fun test3(workers: Array<Worker>) {
val common = AtomicReference<Data>()
val futures = Array(workers.size, { workerIndex ->
workers[workerIndex].schedule(TransferMode.CHECKED, { Pair(common, workerIndex) }) {
(place, index) ->
val mine = Data(index).freeze()
// Try to publish our own data, until successful, in a tight loop.
while (place.compareAndSwap(null, mine) != null) {}
}
})
val seen = mutableSetOf<Data>()
for (i in 0 until workers.size) {
do {
val current = common.get()
if (current != null && !seen.contains(current)) {
seen += current
// Let others publish.
assertEquals(common.compareAndSwap(current, null), current)
break
}
} while (true)
}
futures.forEach {
it.result()
}
assertEquals(seen.size, workers.size)
}

fun test4() {
assertFailsWith<InvalidMutabilityException> {
AtomicReference(Data(1))
}
assertFailsWith<InvalidMutabilityException> {
AtomicReference<Data>().compareAndSwap(null, Data(2))
}
}

@Test fun runTest() {
val COUNT = 20
val workers = Array(COUNT, { _ -> startWorker()})

test1(workers)
test2(workers)
test3(workers)
test4()
}
70 changes: 70 additions & 0 deletions runtime/src/main/cpp/Atomic.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "Atomic.h"
#include "Common.h"
#include "Types.h"

namespace {

template <typename T> T addAndGetImpl(KRef thiz, T delta) {
volatile T* location = reinterpret_cast<volatile T*>(thiz + 1);
return atomicAdd(location, delta);
}

template <typename T> T compareAndSwapImpl(KRef thiz, T expectedValue, T newValue) {
volatile T* location = reinterpret_cast<volatile T*>(thiz + 1);
return compareAndSwap(location, expectedValue, newValue);
}

} // namespace

extern "C" {

RUNTIME_NORETURN void ThrowInvalidMutabilityException();

KInt Kotlin_AtomicInt_addAndGet(KRef thiz, KInt delta) {
return addAndGetImpl(thiz, delta);
}

KInt Kotlin_AtomicInt_compareAndSwap(KRef thiz, KInt expectedValue, KInt newValue) {
return compareAndSwapImpl(thiz, expectedValue, newValue);
}

KLong Kotlin_AtomicLong_addAndGet(KRef thiz, KLong delta) {
return addAndGetImpl(thiz, delta);
}

KLong Kotlin_AtomicLong_compareAndSwap(KRef thiz, KLong expectedValue, KLong newValue) {
return compareAndSwapImpl(thiz, expectedValue, newValue);
}

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

void Kotlin_AtomicReference_checkIfFrozen(KRef value) {
if (value != nullptr && !value->container()->permanentOrFrozen()) {
ThrowInvalidMutabilityException();
}
}

KRef Kotlin_AtomicReference_compareAndSwap(KRef thiz, KRef expectedValue, KRef newValue) {
Kotlin_AtomicReference_checkIfFrozen(newValue);
return compareAndSwapImpl(thiz, expectedValue, newValue);
}

} // extern "C"
16 changes: 15 additions & 1 deletion runtime/src/main/cpp/Atomic.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,27 @@

#include "Common.h"

ALWAYS_INLINE inline int atomicAdd(int* where, int what) {
template <typename T>
ALWAYS_INLINE inline T atomicAdd(volatile T* where, T what) {
#ifndef KONAN_NO_THREADS
return __sync_add_and_fetch(where, what);
#else
return *where += 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
}


#endif // RUNTIME_ATOMIC_H
3 changes: 0 additions & 3 deletions runtime/src/main/cpp/Worker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@
#if WITH_WORKERS
#include <pthread.h>
#include <sys/time.h>

#include <deque>
#include <unordered_map>
#endif

#include "Alloc.h"
Expand Down
2 changes: 1 addition & 1 deletion runtime/src/main/kotlin/konan/BinaryBlob.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import kotlinx.cinterop.*
* An immutable compile-time array of bytes.
*/
@ExportTypeInfo("theImmutableBinaryBlobTypeInfo")
@Immutable
@Frozen
public final class ImmutableBinaryBlob private constructor() {
public val size: Int
get() = getArrayLength()
Expand Down
5 changes: 3 additions & 2 deletions runtime/src/main/kotlin/konan/internal/Annotations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ annotation class ExportForCompiler
annotation class InlineConstructor

/**
* Class is immutable and is frozen by default.
* Class is frozen by default. Also this annotation is (ab)used for marking objects
* where mutability checks are not needed, and they are shared, such as atomics.
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class Immutable
internal annotation class Frozen
16 changes: 8 additions & 8 deletions runtime/src/main/kotlin/konan/internal/Boxing.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ external fun getCachedLongBox(value: Long): LongBox
@SymbolName("inLongBoxCache")
external fun inLongBoxCache(value: Long): Boolean

@Immutable
@Frozen
class BooleanBox(val value: Boolean) : Comparable<Boolean> {
override fun equals(other: Any?): Boolean {
if (other !is BooleanBox) {
Expand All @@ -65,7 +65,7 @@ fun boxBoolean(value: Boolean) = if (inBooleanBoxCache(value)) {
BooleanBox(value)
}

@Immutable
@Frozen
class CharBox(val value: Char) : Comparable<Char> {
override fun equals(other: Any?): Boolean {
if (other !is CharBox) {
Expand All @@ -89,7 +89,7 @@ fun boxChar(value: Char) = if (inCharBoxCache(value)) {
CharBox(value)
}

@Immutable
@Frozen
class ByteBox(val value: Byte) : Number(), Comparable<Byte> {
override fun equals(other: Any?): Boolean {
if (other !is ByteBox) {
Expand Down Expand Up @@ -121,7 +121,7 @@ fun boxByte(value: Byte) = if (inByteBoxCache(value)) {
ByteBox(value)
}

@Immutable
@Frozen
class ShortBox(val value: Short) : Number(), Comparable<Short> {
override fun equals(other: Any?): Boolean {
if (other !is ShortBox) {
Expand Down Expand Up @@ -153,7 +153,7 @@ fun boxShort(value: Short) = if (inShortBoxCache(value)) {
ShortBox(value)
}

@Immutable
@Frozen
class IntBox(val value: Int) : Number(), Comparable<Int> {
override fun equals(other: Any?): Boolean {
if (other !is IntBox) {
Expand Down Expand Up @@ -185,7 +185,7 @@ fun boxInt(value: Int) = if (inIntBoxCache(value)) {
IntBox(value)
}

@Immutable
@Frozen
class LongBox(val value: Long) : Number(), Comparable<Long> {
override fun equals(other: Any?): Boolean {
if (other !is LongBox) {
Expand Down Expand Up @@ -217,7 +217,7 @@ fun boxLong(value: Long) = if (inLongBoxCache(value)) {
LongBox(value)
}

@Immutable
@Frozen
class FloatBox(val value: Float) : Number(), Comparable<Float> {
override fun equals(other: Any?): Boolean {
if (other !is FloatBox) {
Expand Down Expand Up @@ -245,7 +245,7 @@ class FloatBox(val value: Float) : Number(), Comparable<Float> {
@ExportForCppRuntime("Kotlin_boxFloat")
fun boxFloat(value: Float) = FloatBox(value)

@Immutable
@Frozen
class DoubleBox(val value: Double) : Number(), Comparable<Double> {
override fun equals(other: Any?): Boolean {
if (other !is DoubleBox) {
Expand Down
6 changes: 3 additions & 3 deletions runtime/src/main/kotlin/konan/internal/InteropBoxing.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package konan.internal

import kotlinx.cinterop.*

@Immutable
@Frozen
class NativePtrBox(val value: NativePtr) {
override fun equals(other: Any?): Boolean {
if (other !is NativePtrBox) {
Expand All @@ -35,7 +35,7 @@ class NativePtrBox(val value: NativePtr) {

fun boxNativePtr(value: NativePtr) = NativePtrBox(value)

@Immutable
@Frozen
class NativePointedBox(val value: NativePointed) {
override fun equals(other: Any?): Boolean {
if (other !is NativePointedBox) {
Expand All @@ -56,7 +56,7 @@ class NativePointedBox(val value: NativePointed) {
fun boxNativePointed(value: NativePointed?) = if (value != null) NativePointedBox(value) else null
fun unboxNativePointed(box: NativePointedBox?) = box?.value

@Immutable
@Frozen
class CPointerBox(val value: CPointer<CPointed>) : CValuesRef<CPointed>() {
override fun equals(other: Any?): Boolean {
if (other !is CPointerBox) {
Expand Down
Loading

0 comments on commit 94fb9c0

Please sign in to comment.