[K/N] Rewrite AtomicReference using intrinsics

^KT-59120
This commit is contained in:
Pavel Kunyavskiy
2023-06-07 12:09:31 +02:00
committed by Space Team
parent 8fad4272fc
commit 2f36886bac
8 changed files with 25 additions and 450 deletions
@@ -1,90 +0,0 @@
/*
* 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 "Exceptions.h"
#include "Memory.h"
#include "Types.h"
namespace {
struct AtomicReferenceLayout {
ObjHeader header;
KRef value_;
KInt lock_;
KInt cookie_;
};
inline AtomicReferenceLayout* asAtomicReference(KRef thiz) {
return reinterpret_cast<AtomicReferenceLayout*>(thiz);
}
} // namespace
extern "C" {
void Kotlin_AtomicReference_checkIfFrozen(KRef value) {
if (!kotlin::compiler::freezingEnabled()) {
return;
}
if (value != nullptr && !isPermanentOrFrozen(value)) {
ThrowInvalidMutabilityException(value);
}
}
OBJ_GETTER(Kotlin_AtomicReference_compareAndSwap, KRef thiz, KRef expectedValue, KRef newValue) {
if (isPermanentOrFrozen(thiz)) {
Kotlin_AtomicReference_checkIfFrozen(newValue);
}
// See Kotlin_AtomicReference_get() for explanations, why locking is needed.
AtomicReferenceLayout* ref = asAtomicReference(thiz);
RETURN_RESULT_OF(SwapHeapRefLocked, &ref->value_, expectedValue, newValue,
&ref->lock_, &ref->cookie_);
}
KBoolean Kotlin_AtomicReference_compareAndSet(KRef thiz, KRef expectedValue, KRef newValue) {
if (isPermanentOrFrozen(thiz)) {
Kotlin_AtomicReference_checkIfFrozen(newValue);
}
// See Kotlin_AtomicReference_get() for explanations, why locking is needed.
AtomicReferenceLayout* ref = asAtomicReference(thiz);
ObjHolder holder;
auto old = SwapHeapRefLocked(&ref->value_, expectedValue, newValue,
&ref->lock_, &ref->cookie_, holder.slot());
return old == expectedValue;
}
void Kotlin_AtomicReference_set(KRef thiz, KRef newValue) {
if (isPermanentOrFrozen(thiz)) {
Kotlin_AtomicReference_checkIfFrozen(newValue);
}
AtomicReferenceLayout* ref = asAtomicReference(thiz);
SetHeapRefLocked(&ref->value_, newValue, &ref->lock_, &ref->cookie_);
}
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
// rescheduled unluckily, between the moment value is read from the field and RC is incremented,
// object may go away.
AtomicReferenceLayout* ref = asAtomicReference(thiz);
RETURN_RESULT_OF(ReadHeapRefLocked, &ref->value_, &ref->lock_, &ref->cookie_);
}
} // extern "C"
@@ -19,11 +19,7 @@ import kotlin.native.isExperimentalMM
* the returned instance as it may cause accidental deadlock. Also this behavior can be changed in the future.
*/
@OptIn(kotlin.ExperimentalStdlibApi::class, FreezingIsDeprecated::class)
public actual fun <T> lazy(initializer: () -> T): Lazy<T> =
if (isExperimentalMM())
SynchronizedLazyImpl(initializer)
else
FreezeAwareLazyImpl(initializer)
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
/**
@@ -39,8 +35,8 @@ public actual fun <T> lazy(initializer: () -> T): Lazy<T> =
@OptIn(kotlin.ExperimentalStdlibApi::class, FreezingIsDeprecated::class)
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> if (isExperimentalMM()) SynchronizedLazyImpl(initializer) else throw UnsupportedOperationException()
LazyThreadSafetyMode.PUBLICATION -> if (isExperimentalMM()) SafePublicationLazyImpl(initializer) else FreezeAwareLazyImpl(initializer)
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
@@ -15,13 +15,7 @@ import kotlin.native.concurrent.*
/**
* An [Int] value that is always updated atomically.
* For additional details about atomicity guarantees for reads and writes see [kotlin.concurrent.Volatile].
*
* Legacy MM: Atomic values and freezing: this type is unique with regard to freezing.
* Namely, it provides mutating operations, while can participate in frozen subgraphs.
* So shared frozen objects can have mutable fields of [AtomicInt] type.
*/
@Frozen
@OptIn(FreezingIsDeprecated::class, ExperimentalStdlibApi::class)
@SinceKotlin("1.9")
public class AtomicInt(@Volatile public var value: Int) {
/**
@@ -103,12 +97,7 @@ public class AtomicInt(@Volatile public var value: Int) {
* A [Long] value that is always updated atomically.
* For additional details about atomicity guarantees for reads and writes see [kotlin.concurrent.Volatile].
*
* Legacy MM: Atomic values and freezing: this type is unique with regard to freezing.
* Namely, it provides mutating operations, while can participate in frozen subgraphs.
* So shared frozen objects can have mutable fields of [AtomicLong] type.
*/
@Frozen
@OptIn(FreezingIsDeprecated::class, ExperimentalStdlibApi::class)
@SinceKotlin("1.9")
public class AtomicLong(@Volatile public var value: Long) {
/**
@@ -194,64 +183,15 @@ public class AtomicLong(@Volatile public var value: Long) {
/**
* An object reference that is always updated atomically.
*
* Legacy MM: An atomic reference to a frozen Kotlin object. Can be used in concurrent scenarious
* but frequently shall be of nullable type and be zeroed out once no longer needed.
* Otherwise memory leak could happen. To detect such leaks [kotlin.native.internal.GC.detectCycles]
* in debug mode could be helpful.
*/
@FrozenLegacyMM
@LeakDetectorCandidate
@NoReorderFields
@OptIn(FreezingIsDeprecated::class)
@SinceKotlin("1.9")
public class AtomicReference<T> {
private var value_: T
// A spinlock to fix potential ARC race.
private var lock: Int = 0
// Optimization for speeding up access.
private var cookie: Int = 0
/**
* Creates a new atomic reference pointing to the [given value][value].
*
* @throws InvalidMutabilityException with legacy MM if reference is not frozen.
*/
constructor(value: T) {
if (this.isFrozen) {
checkIfFrozen(value)
}
value_ = value
}
/**
* The current value.
* Gets the current value or sets to the given [new value][newValue].
*
* Legacy MM: if the [new value][newValue] value is not null, it must be frozen or permanent object.
*
* @throws InvalidMutabilityException with legacy MM if the value is not frozen or a permanent object
*/
public var value: T
get() = @Suppress("UNCHECKED_CAST")(getImpl() as T)
set(newValue) = setImpl(newValue)
public class AtomicReference<T>(public @Volatile var value: T) {
/**
* Atomically sets the value to the given [new value][newValue] and returns the old value.
*/
public fun getAndSet(newValue: T): T {
while (true) {
val old = value
if (old === newValue) {
return old
}
if (compareAndSet(old, newValue)) {
return old
}
}
}
public fun getAndSet(newValue: T): T = this::value.getAndSetField(newValue)
/**
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected],
@@ -261,8 +201,7 @@ public class AtomicReference<T> {
*
* Comparison of values is done by reference.
*/
@GCUnsafeCall("Kotlin_AtomicReference_compareAndSet")
external public fun compareAndSet(expected: T, newValue: T): Boolean
public fun compareAndSet(expected: T, newValue: T): Boolean = this::value.compareAndSetField(expected, newValue)
/**
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected]
@@ -271,26 +210,14 @@ public class AtomicReference<T> {
* Provides sequential consistent ordering guarantees and cannot fail spuriously.
*
* Comparison of values is done by reference.
*
* Legacy MM: if the [new value][newValue] value is not null, it must be frozen or permanent object.
*
* @throws InvalidMutabilityException with legacy MM if the value is not frozen or a permanent object
*/
@GCUnsafeCall("Kotlin_AtomicReference_compareAndSwap")
external public fun compareAndExchange(expected: T, newValue: T): T
public fun compareAndExchange(expected: T, newValue: T): T = this::value.compareAndExchangeField(expected, newValue)
/**
* Returns the string representation of the current [value].
*/
public override fun toString(): String =
"${debugString(this)} -> ${debugString(value)}"
// Implementation details.
@GCUnsafeCall("Kotlin_AtomicReference_set")
private external fun setImpl(newValue: Any?): Unit
@GCUnsafeCall("Kotlin_AtomicReference_get")
private external fun getImpl(): Any?
}
/**
@@ -299,13 +226,7 @@ public class AtomicReference<T> {
*
* [kotlinx.cinterop.NativePtr] is a value type, hence it is stored in [AtomicNativePtr] without boxing
* and [compareAndSet], [compareAndExchange] operations perform comparison by value.
*
* Legacy MM: Atomic values and freezing: this type is unique with regard to freezing.
* Namely, it provides mutating operations, while can participate in frozen subgraphs.
* So shared frozen objects can have mutable fields of [AtomicNativePtr] type.
*/
@Frozen
@OptIn(FreezingIsDeprecated::class, ExperimentalStdlibApi::class)
@SinceKotlin("1.9")
@ExperimentalForeignApi
public class AtomicNativePtr(@Volatile public var value: NativePtr) {
@@ -376,7 +297,6 @@ private fun debugString(value: Any?): String {
*
* Returns true if the actual field value matched [expectedValue]
*
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
*/
@PublishedApi
@TypedIntrinsic(IntrinsicType.COMPARE_AND_SET_FIELD)
@@ -398,7 +318,6 @@ internal external fun <T> KMutableProperty0<T>.compareAndSetField(expectedValue:
*
* Returns the field value before operation.
*
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
*/
@PublishedApi
@TypedIntrinsic(IntrinsicType.COMPARE_AND_EXCHANGE_FIELD)
@@ -414,8 +333,6 @@ internal external fun <T> KMutableProperty0<T>.compareAndExchangeField(expectedV
* would be thrown.
*
* If property referenced by [this] has nontrivial setter it will not be called.
*
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
*/
@PublishedApi
@TypedIntrinsic(IntrinsicType.GET_AND_SET_FIELD)
@@ -432,8 +349,6 @@ internal external fun <T> KMutableProperty0<T>.getAndSetField(newValue: T): T
* would be thrown.
*
* If property referenced by [this] has nontrivial setter it will not be called.
*
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
*/
@PublishedApi
@TypedIntrinsic(IntrinsicType.GET_AND_ADD_FIELD)
@@ -449,8 +364,6 @@ internal external fun KMutableProperty0<Short>.getAndAddField(delta: Short): Sho
* would be thrown.
*
* If property referenced by [this] has nontrivial setter it will not be called.
*
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
*/
@PublishedApi
@TypedIntrinsic(IntrinsicType.GET_AND_ADD_FIELD)
@@ -466,8 +379,6 @@ internal external fun KMutableProperty0<Int>.getAndAddField(newValue: Int): Int
* would be thrown.
*
* If property referenced by [this] has nontrivial setter it will not be called.
*
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
*/
@PublishedApi
@TypedIntrinsic(IntrinsicType.GET_AND_ADD_FIELD)
@@ -483,8 +394,6 @@ internal external fun KMutableProperty0<Long>.getAndAddField(newValue: Long): Lo
* would be thrown.
*
* If property referenced by [this] has nontrivial setter it will not be called.
*
* Legacy MM: if [this] is a reference for a non-value represented field, [IllegalArgumentException] would be thrown.
*/
@PublishedApi
@TypedIntrinsic(IntrinsicType.GET_AND_ADD_FIELD)
@@ -6,7 +6,7 @@
package kotlin.coroutines
import kotlin.*
import kotlin.native.concurrent.*
import kotlin.concurrent.*
import kotlin.coroutines.intrinsics.CoroutineSingletons.*
import kotlin.coroutines.intrinsics.*
import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
@@ -25,8 +25,7 @@ internal actual constructor(
public actual override val context: CoroutineContext
get() = delegate.context
@Suppress("DEPRECATION")
private var resultRef = FreezableAtomicReference<Any?>(initialResult)
private var resultRef = AtomicReference<Any?>(initialResult)
public actual override fun resumeWith(result: Result<T>) {
while (true) {
@@ -61,7 +61,7 @@ public typealias ReportUnhandledExceptionHook = Function1<Throwable, Unit>
@OptIn(FreezingIsDeprecated::class)
public fun setUnhandledExceptionHook(hook: ReportUnhandledExceptionHook?): ReportUnhandledExceptionHook? {
try {
return UnhandledExceptionHookHolder.hook.swap(hook)
return UnhandledExceptionHookHolder.hook.getAndSet(hook)
} catch (e: InvalidMutabilityException) {
throw InvalidMutabilityException("Unhandled exception hook must be frozen")
}
@@ -72,7 +72,7 @@ public fun setUnhandledExceptionHook(hook: ReportUnhandledExceptionHook?): Repor
@OptIn(FreezingIsDeprecated::class, ExperimentalNativeApi::class)
public fun setUnhandledExceptionHook(hook: ReportUnhandledExceptionHook): ReportUnhandledExceptionHook? {
try {
return UnhandledExceptionHookHolder.hook.swap(hook)
return UnhandledExceptionHookHolder.hook.getAndSet(hook)
} catch (e: InvalidMutabilityException) {
throw InvalidMutabilityException("Unhandled exception hook must be frozen")
}
@@ -15,13 +15,7 @@ import kotlin.concurrent.*
/**
* An [Int] value that is always updated atomically.
* For additional details about atomicity guarantees for reads and writes see [kotlin.concurrent.Volatile].
*
* Legacy MM: Atomic values and freezing: this type is unique with regard to freezing.
* Namely, it provides mutating operations, while can participate in frozen subgraphs.
* So shared frozen objects can have mutable fields of [AtomicInt] type.
*/
@Frozen
@OptIn(FreezingIsDeprecated::class, ExperimentalStdlibApi::class)
@Deprecated("Use kotlin.concurrent.AtomicInt instead.", ReplaceWith("kotlin.concurrent.AtomicInt"))
@DeprecatedSinceKotlin(warningSince = "1.9")
public class AtomicInt(public @Volatile var value: Int) {
@@ -101,13 +95,7 @@ public class AtomicInt(public @Volatile var value: Int) {
/**
* A [Long] value that is always updated atomically.
* For additional details about atomicity guarantees for reads and writes see [kotlin.concurrent.Volatile].
*
* Legacy MM: Atomic values and freezing: this type is unique with regard to freezing.
* Namely, it provides mutating operations, while can participate in frozen subgraphs.
* So shared frozen objects can have mutable fields of [AtomicLong] type.
*/
@Frozen
@OptIn(FreezingIsDeprecated::class, ExperimentalStdlibApi::class)
@Deprecated("Use kotlin.concurrent.AtomicLong instead.", ReplaceWith("kotlin.concurrent.AtomicLong"))
@DeprecatedSinceKotlin(warningSince = "1.9")
public class AtomicLong(public @Volatile var value: Long = 0L) {
@@ -192,65 +180,14 @@ public class AtomicLong(public @Volatile var value: Long = 0L) {
/**
* An object reference that is always updated atomically.
*
* Legacy MM: An atomic reference to a frozen Kotlin object. Can be used in concurrent scenarious
* but frequently shall be of nullable type and be zeroed out once no longer needed.
* Otherwise memory leak could happen. To detect such leaks [kotlin.native.runtime.GC.detectCycles]
* in debug mode could be helpful.
*/
@FrozenLegacyMM
@LeakDetectorCandidate
@NoReorderFields
@OptIn(FreezingIsDeprecated::class)
@Deprecated("Use kotlin.concurrent.AtomicReference instead.", ReplaceWith("kotlin.concurrent.AtomicReference"))
@DeprecatedSinceKotlin(warningSince = "1.9")
public class AtomicReference<T> {
private var value_: T
// A spinlock to fix potential ARC race.
private var lock: Int = 0
// Optimization for speeding up access.
private var cookie: Int = 0
/**
* Creates a new atomic reference pointing to the [given value][value].
*
* @throws InvalidMutabilityException with legacy MM if reference is not frozen.
*/
constructor(value: T) {
if (this.isFrozen) {
checkIfFrozen(value)
}
value_ = value
}
/**
* The current value.
* Gets the current value or sets to the given [new value][newValue].
*
* Legacy MM: if the [new value][newValue] value is not null, it must be frozen or permanent object.
*
* @throws InvalidMutabilityException with legacy MM if the value is not frozen or a permanent object
*/
public var value: T
get() = @Suppress("UNCHECKED_CAST")(getImpl() as T)
set(newValue) = setImpl(newValue)
public class AtomicReference<T>(public @Volatile var value: T) {
/**
* Atomically sets the value to the given [new value][newValue] and returns the old value.
*/
public fun getAndSet(newValue: T): T {
while (true) {
val old = value
if (old === newValue) {
return old
}
if (compareAndSet(old, newValue)) {
return old
}
}
}
public fun getAndSet(newValue: T): T = this::value.getAndSetField(newValue)
/**
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected],
@@ -260,8 +197,7 @@ public class AtomicReference<T> {
*
* Comparison of values is done by reference.
*/
@GCUnsafeCall("Kotlin_AtomicReference_compareAndSet")
external public fun compareAndSet(expected: T, newValue: T): Boolean
public fun compareAndSet(expected: T, newValue: T): Boolean = this::value.compareAndSetField(expected, newValue)
/**
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected]
@@ -270,26 +206,14 @@ public class AtomicReference<T> {
* Provides sequential consistent ordering guarantees and cannot fail spuriously.
*
* Comparison of values is done by reference.
*
* Legacy MM: if the [new value][newValue] value is not null, it must be frozen or permanent object.
*
* @throws InvalidMutabilityException with legacy MM if the value is not frozen or a permanent object
*/
@GCUnsafeCall("Kotlin_AtomicReference_compareAndSwap")
external public fun compareAndSwap(expected: T, newValue: T): T
public fun compareAndSwap(expected: T, newValue: T): T = this::value.compareAndExchangeField(expected, newValue)
/**
* Returns the string representation of this object.
*/
public override fun toString(): String =
"${debugString(this)} -> ${debugString(value)}"
// Implementation details.
@GCUnsafeCall("Kotlin_AtomicReference_set")
private external fun setImpl(newValue: Any?): Unit
@GCUnsafeCall("Kotlin_AtomicReference_get")
private external fun getImpl(): Any?
}
/**
@@ -298,13 +222,7 @@ public class AtomicReference<T> {
*
* [kotlinx.cinterop.NativePtr] is a value type, hence it is stored in [AtomicNativePtr] without boxing
* and [compareAndSet], [compareAndSwap] operations perform comparison by value.
*
* Legacy MM: Atomic values and freezing: this type is unique with regard to freezing.
* Namely, it provides mutating operations, while can participate in frozen subgraphs.
* So shared frozen objects can have mutable fields of [AtomicNativePtr] type.
*/
@Frozen
@OptIn(FreezingIsDeprecated::class, ExperimentalStdlibApi::class)
@Deprecated("Use kotlin.concurrent.AtomicNativePtr instead.", ReplaceWith("kotlin.concurrent.AtomicNativePtr"))
@DeprecatedSinceKotlin(warningSince = "1.9")
public class AtomicNativePtr(public @Volatile var value: NativePtr) {
@@ -360,62 +278,23 @@ private fun debugString(value: Any?): String {
}
/**
* Note: this class is useful only with legacy memory manager. Please use [AtomicReference] instead.
*
* An atomic reference to a Kotlin object. Can be used in concurrent scenarious, but must be frozen first,
* otherwise behaves as regular box for the value. If frozen, shall be zeroed out once no longer needed.
* Otherwise memory leak could happen. To detect such leaks [kotlin.native.runtime.GC.detectCycles]
* in debug mode could be helpful.
* This class was useful only with legacy memory manager. Please use [AtomicReference] instead.
*/
@NoReorderFields
@LeakDetectorCandidate
@ExportTypeInfo("theFreezableAtomicReferenceTypeInfo")
@FreezingIsDeprecated
@Deprecated("Use kotlin.concurrent.AtomicReference instead.", ReplaceWith("kotlin.concurrent.AtomicReference"))
@DeprecatedSinceKotlin(warningSince = "1.9")
public class FreezableAtomicReference<T>(private var value_: T) {
// A spinlock to fix potential ARC race.
private var lock: Int = 0
// Optimization for speeding up access.
private var cookie: Int = 0
/**
* The referenced value.
* Gets the value or sets to the given [new value][newValue]. If the [new value][newValue] is not null,
* and `this` is frozen - it must be frozen or permanent object.
*
* @throws InvalidMutabilityException if the value is not frozen or a permanent object
*/
public var value: T
get() = @Suppress("UNCHECKED_CAST")(getImpl() as T)
set(newValue) {
if (this.isShareable())
setImpl(newValue)
else
value_ = newValue
}
public class FreezableAtomicReference<T>(public @Volatile var value: T) {
/**
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected]
* and returns the old value in any case.
*
* Legacy MM: If the [new value][newValue] value is not null and object is frozen, it must be frozen or permanent object.
*
* @param expected the expected value
* @param newValue the new value
* @throws InvalidMutabilityException with legacy MM if the value is not frozen or a permanent object
* @return the old value
*/
public fun compareAndSwap(expected: T, newValue: T): T {
return if (this.isShareable()) {
@Suppress("UNCHECKED_CAST")(compareAndSwapImpl(expected, newValue) as T)
} else {
val old = value_
if (old === expected) value_ = newValue
old
}
}
public fun compareAndSwap(expected: T, newValue: T): T = this::value.compareAndExchangeField(expected, newValue)
/**
* Atomically sets the value to the given [new value][newValue] if the current value equals the [expected value][expected]
@@ -427,17 +306,7 @@ public class FreezableAtomicReference<T>(private var value_: T) {
* @param newValue the new value
* @return true if successful
*/
public fun compareAndSet(expected: T, newValue: T): Boolean {
if (this.isShareable())
return compareAndSetImpl(expected, newValue)
val old = value_
if (old === expected) {
value_ = newValue
return true
} else {
return false
}
}
public fun compareAndSet(expected: T, newValue: T): Boolean = this::value.compareAndSetField(expected, newValue)
/**
* Returns the string representation of this object.
@@ -447,29 +316,4 @@ public class FreezableAtomicReference<T>(private var value_: T) {
public override fun toString(): String =
"${debugString(this)} -> ${debugString(value)}"
// TODO: Consider making this public.
internal fun swap(newValue: T): T {
while (true) {
val old = value
if (old === newValue) {
return old
}
if (compareAndSet(old, newValue)) {
return old
}
}
}
// Implementation details.
@GCUnsafeCall("Kotlin_AtomicReference_set")
private external fun setImpl(newValue: Any?): Unit
@GCUnsafeCall("Kotlin_AtomicReference_get")
private external fun getImpl(): Any?
@GCUnsafeCall("Kotlin_AtomicReference_compareAndSwap")
private external fun compareAndSwapImpl(expected: Any?, newValue: Any?): Any?
@GCUnsafeCall("Kotlin_AtomicReference_compareAndSet")
private external fun compareAndSetImpl(expected: Any?, newValue: Any?): Boolean
}
@@ -12,68 +12,6 @@ import kotlin.native.internal.Frozen
import kotlin.concurrent.AtomicReference
import kotlinx.cinterop.ExperimentalForeignApi
@FreezingIsDeprecated
internal class FreezeAwareLazyImpl<out T>(initializer: () -> T) : Lazy<T> {
private val value_ = FreezableAtomicReference<Any?>(UNINITIALIZED)
// This cannot be made atomic because of the legacy MM. See https://github.com/JetBrains/kotlin-native/pull/3944
// So it must be protected by the lock below.
private var initializer_: (() -> T)? = initializer
private val lock_ = Lock()
private fun getOrInit(doFreeze: Boolean): T {
var result = value_.value
if (result !== UNINITIALIZED) {
if (result === INITIALIZING) {
value_.value = UNINITIALIZED
throw IllegalStateException("Recursive lazy computation")
}
@Suppress("UNCHECKED_CAST")
return result as T
}
// Set value_ to INITIALIZING.
value_.value = INITIALIZING
try {
result = initializer_!!()
if (doFreeze) result.freeze()
} catch (throwable: Throwable) {
value_.value = UNINITIALIZED
throw throwable
}
if (!doFreeze) {
if (this.isFrozen) {
value_.value = UNINITIALIZED
throw InvalidMutabilityException("Frozen during lazy computation")
}
// Clear initializer.
initializer_ = null
}
// Set value_ to actual one.
value_.value = result
return result
}
override val value: T
get() {
return if (isShareable()) {
// TODO: This is probably a big performance problem for lazy with the new MM. Address it.
locked(lock_) {
getOrInit(isFrozen)
}
} else {
getOrInit(false)
}
}
/**
* This operation on shared objects may return value which is no longer reflect the current state of lazy.
*/
override fun isInitialized(): Boolean = (value_.value !== UNINITIALIZED) && (value_.value !== INITIALIZING)
override fun toString(): String = if (isInitialized())
value.toString() else "Lazy value not initialized yet"
}
@OptIn(FreezingIsDeprecated::class)
internal object UNINITIALIZED {
// So that single-threaded configs can use those as well.
@@ -137,8 +75,8 @@ public fun <T> atomicLazy(initializer: () -> T): Lazy<T> = AtomicLazyImpl(initia
@Suppress("UNCHECKED_CAST")
@OptIn(FreezingIsDeprecated::class)
internal class SynchronizedLazyImpl<out T>(initializer: () -> T) : Lazy<T> {
private var initializer = FreezableAtomicReference<(() -> T)?>(initializer)
private var valueRef = FreezableAtomicReference<Any?>(UNINITIALIZED)
private var initializer = AtomicReference<(() -> T)?>(initializer)
private var valueRef = AtomicReference<Any?>(UNINITIALIZED)
private val lock = Lock()
override val value: T
@@ -151,14 +89,7 @@ internal class SynchronizedLazyImpl<out T>(initializer: () -> T) : Lazy<T> {
return locked(lock) {
val _v2 = valueRef.value
if (_v2 === UNINITIALIZED) {
val wasFrozen = this.isFrozen
val typedValue = initializer.value!!()
if (this.isFrozen) {
if (!wasFrozen) {
throw InvalidMutabilityException("Frozen during lazy computation")
}
typedValue.freeze()
}
valueRef.value = typedValue
initializer.value = null
typedValue
@@ -175,10 +106,9 @@ internal class SynchronizedLazyImpl<out T>(initializer: () -> T) : Lazy<T> {
@Suppress("UNCHECKED_CAST")
@OptIn(FreezingIsDeprecated::class)
internal class SafePublicationLazyImpl<out T>(initializer: () -> T) : Lazy<T> {
private var initializer = FreezableAtomicReference<(() -> T)?>(initializer)
private var valueRef = FreezableAtomicReference<Any?>(UNINITIALIZED)
private var initializer = AtomicReference<(() -> T)?>(initializer)
private var valueRef = AtomicReference<Any?>(UNINITIALIZED)
override val value: T
get() {
@@ -190,14 +120,7 @@ internal class SafePublicationLazyImpl<out T>(initializer: () -> T) : Lazy<T> {
val initializerValue = initializer.value
// if we see null in initializer here, it means that the value is already set by another thread
if (initializerValue != null) {
val wasFrozen = this.isFrozen
val newValue = initializerValue()
if (this.isFrozen) {
if (!wasFrozen) {
throw InvalidMutabilityException("Frozen during lazy computation")
}
newValue.freeze()
}
if (valueRef.compareAndSet(UNINITIALIZED, newValue)) {
initializer.value = null
return newValue
@@ -10,9 +10,8 @@ package kotlin.native.internal
import kotlin.experimental.ExperimentalNativeApi
import kotlin.internal.getProgressionLastElement
import kotlin.reflect.KClass
import kotlin.native.concurrent.freeze
import kotlin.concurrent.AtomicReference
import kotlinx.cinterop.*
import kotlin.native.concurrent.FreezableAtomicReference
@ExportForCppRuntime
@PublishedApi
@@ -158,12 +157,7 @@ internal fun ReportUnhandledException(throwable: Throwable) {
// to throw an exception during it's initialization before this hook would've been initialized.
@OptIn(FreezingIsDeprecated::class, ExperimentalNativeApi::class)
internal object UnhandledExceptionHookHolder {
internal val hook: FreezableAtomicReference<ReportUnhandledExceptionHook?> =
if (Platform.memoryModel == MemoryModel.EXPERIMENTAL) {
FreezableAtomicReference<ReportUnhandledExceptionHook?>(null)
} else {
FreezableAtomicReference<ReportUnhandledExceptionHook?>(null).freeze()
}
internal val hook: AtomicReference<ReportUnhandledExceptionHook?> = AtomicReference<ReportUnhandledExceptionHook?>(null)
}
// TODO: Can be removed only when native-mt coroutines stop using it.