[K/N] Change AtomicReference for the new MM ^KT-50026
Make AtomicReference behave like FreezableAtomicReference with the new MM: not frozen by default, don't require freezing value unless the ref itself is frozen. The behaviour with the old MM is unchanged. Merge-request: KT-MR-5155
This commit is contained in:
committed by
Space
parent
1d2e60a2af
commit
4fd61ad1b0
+1
@@ -26,6 +26,7 @@ object KonanFqNames {
|
|||||||
val threadLocal = FqName("kotlin.native.concurrent.ThreadLocal")
|
val threadLocal = FqName("kotlin.native.concurrent.ThreadLocal")
|
||||||
val sharedImmutable = FqName("kotlin.native.concurrent.SharedImmutable")
|
val sharedImmutable = FqName("kotlin.native.concurrent.SharedImmutable")
|
||||||
val frozen = FqName("kotlin.native.internal.Frozen")
|
val frozen = FqName("kotlin.native.internal.Frozen")
|
||||||
|
val frozenLegacyMM = FqName("kotlin.native.internal.FrozenLegacyMM")
|
||||||
val leakDetectorCandidate = FqName("kotlin.native.internal.LeakDetectorCandidate")
|
val leakDetectorCandidate = FqName("kotlin.native.internal.LeakDetectorCandidate")
|
||||||
val canBePrecreated = FqName("kotlin.native.internal.CanBePrecreated")
|
val canBePrecreated = FqName("kotlin.native.internal.CanBePrecreated")
|
||||||
val typedIntrinsic = FqName("kotlin.native.internal.TypedIntrinsic")
|
val typedIntrinsic = FqName("kotlin.native.internal.TypedIntrinsic")
|
||||||
|
|||||||
+12
-5
@@ -265,10 +265,17 @@ fun IrDeclaration.findTopLevelDeclaration(): IrDeclaration = when {
|
|||||||
(this.parent as IrDeclaration).findTopLevelDeclaration()
|
(this.parent as IrDeclaration).findTopLevelDeclaration()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val IrClass.isFrozen: Boolean
|
internal fun IrClass.isFrozen(context: Context): Boolean {
|
||||||
get() = annotations.hasAnnotation(KonanFqNames.frozen) ||
|
val isLegacyMM = context.memoryModel != MemoryModel.EXPERIMENTAL
|
||||||
// RTTI is used for non-reference type box:
|
return when {
|
||||||
!this.defaultType.binaryTypeIsReference()
|
!context.config.freezing.freezeImplicit -> false
|
||||||
|
annotations.hasAnnotation(KonanFqNames.frozen) -> true
|
||||||
|
annotations.hasAnnotation(KonanFqNames.frozenLegacyMM) && isLegacyMM -> true
|
||||||
|
// RTTI is used for non-reference type box:
|
||||||
|
!this.defaultType.binaryTypeIsReference() -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun IrConstructorCall.getAnnotationStringValue() = getValueArgument(0).safeAs<IrConst<String>>()?.value
|
fun IrConstructorCall.getAnnotationStringValue() = getValueArgument(0).safeAs<IrConst<String>>()?.value
|
||||||
|
|
||||||
@@ -305,4 +312,4 @@ fun IrFunction.externalSymbolOrThrow(): String? {
|
|||||||
val IrFunction.isBuiltInOperator get() = origin == IrBuiltIns.BUILTIN_OPERATOR
|
val IrFunction.isBuiltInOperator get() = origin == IrBuiltIns.BUILTIN_OPERATOR
|
||||||
|
|
||||||
fun IrDeclaration.isFromMetadataInteropLibrary() =
|
fun IrDeclaration.isFromMetadataInteropLibrary() =
|
||||||
descriptor.module.isFromInteropLibrary()
|
descriptor.module.isFromInteropLibrary()
|
||||||
|
|||||||
+19
-16
@@ -56,16 +56,20 @@ internal enum class ObjectStorageKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: maybe unannotated singleton objects shall be accessed from main thread only as well?
|
// TODO: maybe unannotated singleton objects shall be accessed from main thread only as well?
|
||||||
internal val IrField.storageKind: FieldStorageKind get() {
|
internal fun IrField.storageKind(context: Context): FieldStorageKind {
|
||||||
// TODO: Is this correct?
|
// TODO: Is this correct?
|
||||||
val annotations = correspondingPropertySymbol?.owner?.annotations ?: annotations
|
val annotations = correspondingPropertySymbol?.owner?.annotations ?: annotations
|
||||||
|
val isLegacyMM = context.memoryModel != MemoryModel.EXPERIMENTAL
|
||||||
|
// TODO: simplify, once IR types are fully there.
|
||||||
|
val typeAnnotations = (type.classifierOrNull?.owner as? IrAnnotationContainer)?.annotations
|
||||||
|
val typeFrozen = typeAnnotations?.hasAnnotation(KonanFqNames.frozen) == true ||
|
||||||
|
(typeAnnotations?.hasAnnotation(KonanFqNames.frozenLegacyMM) == true && isLegacyMM)
|
||||||
return when {
|
return when {
|
||||||
annotations.hasAnnotation(KonanFqNames.threadLocal) -> FieldStorageKind.THREAD_LOCAL
|
annotations.hasAnnotation(KonanFqNames.threadLocal) -> FieldStorageKind.THREAD_LOCAL
|
||||||
|
!isLegacyMM && !context.config.freezing.freezeImplicit -> FieldStorageKind.GLOBAL
|
||||||
!isFinal -> FieldStorageKind.GLOBAL
|
!isFinal -> FieldStorageKind.GLOBAL
|
||||||
annotations.hasAnnotation(KonanFqNames.sharedImmutable) -> FieldStorageKind.SHARED_FROZEN
|
annotations.hasAnnotation(KonanFqNames.sharedImmutable) -> FieldStorageKind.SHARED_FROZEN
|
||||||
// TODO: simplify, once IR types are fully there.
|
typeFrozen -> FieldStorageKind.SHARED_FROZEN
|
||||||
(type.classifierOrNull?.owner as? IrAnnotationContainer)
|
|
||||||
?.annotations?.hasAnnotation(KonanFqNames.frozen) == true -> FieldStorageKind.SHARED_FROZEN
|
|
||||||
else -> FieldStorageKind.GLOBAL
|
else -> FieldStorageKind.GLOBAL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,15 +87,14 @@ internal fun IrClass.storageKind(context: Context): ObjectStorageKind = when {
|
|||||||
else -> ObjectStorageKind.SHARED
|
else -> ObjectStorageKind.SHARED
|
||||||
}
|
}
|
||||||
|
|
||||||
val IrField.isGlobalNonPrimitive get() = when {
|
internal fun IrField.isGlobalNonPrimitive(context: Context) = when {
|
||||||
type.computePrimitiveBinaryTypeOrNull() != null -> false
|
type.computePrimitiveBinaryTypeOrNull() != null -> false
|
||||||
else -> storageKind == FieldStorageKind.GLOBAL
|
else -> storageKind(context) == FieldStorageKind.GLOBAL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun IrField.shouldBeFrozen(context: Context): Boolean =
|
internal fun IrField.shouldBeFrozen(context: Context): Boolean =
|
||||||
this.storageKind == FieldStorageKind.SHARED_FROZEN &&
|
this.storageKind(context) == FieldStorageKind.SHARED_FROZEN
|
||||||
(context.memoryModel != MemoryModel.EXPERIMENTAL || context.config.freezing.freezeImplicit)
|
|
||||||
|
|
||||||
internal class RTTIGeneratorVisitor(context: Context) : IrElementVisitorVoid {
|
internal class RTTIGeneratorVisitor(context: Context) : IrElementVisitorVoid {
|
||||||
val generator = RTTIGenerator(context)
|
val generator = RTTIGenerator(context)
|
||||||
@@ -376,7 +379,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
using(parameterScope) usingParameterScope@{
|
using(parameterScope) usingParameterScope@{
|
||||||
using(VariableScope()) usingVariableScope@{
|
using(VariableScope()) usingVariableScope@{
|
||||||
context.llvm.initializersGenerationState.topLevelFields
|
context.llvm.initializersGenerationState.topLevelFields
|
||||||
.filter { it.storageKind != FieldStorageKind.THREAD_LOCAL }
|
.filter { it.storageKind(context) != FieldStorageKind.THREAD_LOCAL }
|
||||||
.filterNot { it.shouldBeInitializedEagerly }
|
.filterNot { it.shouldBeInitializedEagerly }
|
||||||
.forEach { initGlobalField(it) }
|
.forEach { initGlobalField(it) }
|
||||||
ret(null)
|
ret(null)
|
||||||
@@ -393,7 +396,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
using(parameterScope) usingParameterScope@{
|
using(parameterScope) usingParameterScope@{
|
||||||
using(VariableScope()) usingVariableScope@{
|
using(VariableScope()) usingVariableScope@{
|
||||||
context.llvm.initializersGenerationState.topLevelFields
|
context.llvm.initializersGenerationState.topLevelFields
|
||||||
.filter { it.storageKind == FieldStorageKind.THREAD_LOCAL }
|
.filter { it.storageKind(context) == FieldStorageKind.THREAD_LOCAL }
|
||||||
.filterNot { it.shouldBeInitializedEagerly }
|
.filterNot { it.shouldBeInitializedEagerly }
|
||||||
.forEach { initThreadLocalField(it) }
|
.forEach { initThreadLocalField(it) }
|
||||||
ret(null)
|
ret(null)
|
||||||
@@ -494,7 +497,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
appendingTo(bbInit) {
|
appendingTo(bbInit) {
|
||||||
context.llvm.initializersGenerationState.topLevelFields
|
context.llvm.initializersGenerationState.topLevelFields
|
||||||
.filter { !context.useLazyFileInitializers() || it.shouldBeInitializedEagerly }
|
.filter { !context.useLazyFileInitializers() || it.shouldBeInitializedEagerly }
|
||||||
.filterNot { it.storageKind == FieldStorageKind.THREAD_LOCAL }
|
.filterNot { it.storageKind(context) == FieldStorageKind.THREAD_LOCAL }
|
||||||
.forEach { initGlobalField(it) }
|
.forEach { initGlobalField(it) }
|
||||||
context.llvm.initializersGenerationState.moduleGlobalInitializers.forEach {
|
context.llvm.initializersGenerationState.moduleGlobalInitializers.forEach {
|
||||||
evaluateSimpleFunctionCall(it, emptyList(), Lifetime.IRRELEVANT)
|
evaluateSimpleFunctionCall(it, emptyList(), Lifetime.IRRELEVANT)
|
||||||
@@ -510,7 +513,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
}
|
}
|
||||||
context.llvm.initializersGenerationState.topLevelFields
|
context.llvm.initializersGenerationState.topLevelFields
|
||||||
.filter { !context.useLazyFileInitializers() || it.shouldBeInitializedEagerly }
|
.filter { !context.useLazyFileInitializers() || it.shouldBeInitializedEagerly }
|
||||||
.filter { it.storageKind == FieldStorageKind.THREAD_LOCAL }
|
.filter { it.storageKind(context) == FieldStorageKind.THREAD_LOCAL }
|
||||||
.forEach { initThreadLocalField(it) }
|
.forEach { initThreadLocalField(it) }
|
||||||
context.llvm.initializersGenerationState.moduleThreadLocalInitializers.forEach {
|
context.llvm.initializersGenerationState.moduleThreadLocalInitializers.forEach {
|
||||||
evaluateSimpleFunctionCall(it, emptyList(), Lifetime.IRRELEVANT)
|
evaluateSimpleFunctionCall(it, emptyList(), Lifetime.IRRELEVANT)
|
||||||
@@ -531,7 +534,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
context.llvm.initializersGenerationState.topLevelFields
|
context.llvm.initializersGenerationState.topLevelFields
|
||||||
// Only if a subject for memory management.
|
// Only if a subject for memory management.
|
||||||
.forEach { irField ->
|
.forEach { irField ->
|
||||||
if (irField.type.binaryTypeIsReference() && irField.storageKind != FieldStorageKind.THREAD_LOCAL) {
|
if (irField.type.binaryTypeIsReference() && irField.storageKind(context) != FieldStorageKind.THREAD_LOCAL) {
|
||||||
val address = context.llvmDeclarations.forStaticField(irField).storageAddressAccess.getAddress(
|
val address = context.llvmDeclarations.forStaticField(irField).storageAddressAccess.getAddress(
|
||||||
functionGenerationContext
|
functionGenerationContext
|
||||||
)
|
)
|
||||||
@@ -1687,7 +1690,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
if (value.symbol.owner.correspondingPropertySymbol?.owner?.isConst == true) {
|
if (value.symbol.owner.correspondingPropertySymbol?.owner?.isConst == true) {
|
||||||
evaluateConst(value.symbol.owner.initializer?.expression as IrConst<*>).llvm
|
evaluateConst(value.symbol.owner.initializer?.expression as IrConst<*>).llvm
|
||||||
} else {
|
} else {
|
||||||
if (context.config.threadsAreAllowed && value.symbol.owner.isGlobalNonPrimitive) {
|
if (context.config.threadsAreAllowed && value.symbol.owner.isGlobalNonPrimitive(context)) {
|
||||||
functionGenerationContext.checkGlobalsAccessible(currentCodeContext.exceptionHandler)
|
functionGenerationContext.checkGlobalsAccessible(currentCodeContext.exceptionHandler)
|
||||||
}
|
}
|
||||||
val ptr = context.llvmDeclarations.forStaticField(value.symbol.owner).storageAddressAccess.getAddress(
|
val ptr = context.llvmDeclarations.forStaticField(value.symbol.owner).storageAddressAccess.getAddress(
|
||||||
@@ -1706,7 +1709,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
private fun needMutationCheck(irClass: IrClass): Boolean {
|
private fun needMutationCheck(irClass: IrClass): Boolean {
|
||||||
// For now we omit mutation checks on immutable types, as this allows initialization in constructor
|
// 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.
|
// and it is assumed that API doesn't allow to change them.
|
||||||
return !irClass.isFrozen
|
return !irClass.isFrozen(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isZeroConstValue(value: IrExpression): Boolean {
|
private fun isZeroConstValue(value: IrExpression): Boolean {
|
||||||
@@ -1756,7 +1759,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
val globalAddress = context.llvmDeclarations.forStaticField(value.symbol.owner).storageAddressAccess.getAddress(
|
val globalAddress = context.llvmDeclarations.forStaticField(value.symbol.owner).storageAddressAccess.getAddress(
|
||||||
functionGenerationContext
|
functionGenerationContext
|
||||||
)
|
)
|
||||||
if (context.config.threadsAreAllowed && value.symbol.owner.storageKind == FieldStorageKind.GLOBAL)
|
if (context.config.threadsAreAllowed && value.symbol.owner.storageKind(context) == FieldStorageKind.GLOBAL)
|
||||||
functionGenerationContext.checkGlobalsAccessible(currentCodeContext.exceptionHandler)
|
functionGenerationContext.checkGlobalsAccessible(currentCodeContext.exceptionHandler)
|
||||||
if (value.symbol.owner.shouldBeFrozen(context))
|
if (value.symbol.owner.shouldBeFrozen(context))
|
||||||
functionGenerationContext.freeze(valueToAssign, currentCodeContext.exceptionHandler)
|
functionGenerationContext.freeze(valueToAssign, currentCodeContext.exceptionHandler)
|
||||||
|
|||||||
+2
-2
@@ -322,7 +322,7 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
|
|||||||
} else {
|
} else {
|
||||||
// Fields are module-private, so we use internal name:
|
// Fields are module-private, so we use internal name:
|
||||||
val name = "kvar:" + qualifyInternalName(declaration)
|
val name = "kvar:" + qualifyInternalName(declaration)
|
||||||
val storage = if (declaration.storageKind == FieldStorageKind.THREAD_LOCAL) {
|
val storage = if (declaration.storageKind(context) == FieldStorageKind.THREAD_LOCAL) {
|
||||||
addKotlinThreadLocal(name, getLLVMType(declaration.type))
|
addKotlinThreadLocal(name, getLLVMType(declaration.type))
|
||||||
} else {
|
} else {
|
||||||
addKotlinGlobal(name, getLLVMType(declaration.type), isExported = false)
|
addKotlinGlobal(name, getLLVMType(declaration.type), isExported = false)
|
||||||
@@ -416,4 +416,4 @@ private class CodegenStaticFieldMetadata(
|
|||||||
val llvm: StaticFieldLlvmDeclarations
|
val llvm: StaticFieldLlvmDeclarations
|
||||||
) : KonanMetadata(name, konanLibrary), MetadataSource.Property {
|
) : KonanMetadata(name, konanLibrary), MetadataSource.Property {
|
||||||
override val isConst = false
|
override val isConst = false
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -53,7 +53,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
|||||||
|
|
||||||
private fun flagsFromClass(irClass: IrClass): Int {
|
private fun flagsFromClass(irClass: IrClass): Int {
|
||||||
var result = 0
|
var result = 0
|
||||||
if (irClass.isFrozen && context.config.freezing.freezeImplicit)
|
if (irClass.isFrozen(context))
|
||||||
result = result or TF_IMMUTABLE
|
result = result or TF_IMMUTABLE
|
||||||
// TODO: maybe perform deeper analysis to find surely acyclic types.
|
// TODO: maybe perform deeper analysis to find surely acyclic types.
|
||||||
if (!irClass.isInterface && !irClass.isAbstract() && !irClass.isAnnotationClass) {
|
if (!irClass.isInterface && !irClass.isAbstract() && !irClass.isAnnotationClass) {
|
||||||
|
|||||||
+1
-1
@@ -56,7 +56,7 @@ internal class FileInitializersLowering(val context: Context) : FileLoweringPass
|
|||||||
for (declaration in irFile.declarations) {
|
for (declaration in irFile.declarations) {
|
||||||
val irField = (declaration as? IrField) ?: (declaration as? IrProperty)?.backingField
|
val irField = (declaration as? IrField) ?: (declaration as? IrProperty)?.backingField
|
||||||
if (irField == null || !irField.needsInitializationAtRuntime || irField.shouldBeInitializedEagerly) continue
|
if (irField == null || !irField.needsInitializationAtRuntime || irField.shouldBeInitializedEagerly) continue
|
||||||
if (irField.storageKind != FieldStorageKind.THREAD_LOCAL) {
|
if (irField.storageKind(context) != FieldStorageKind.THREAD_LOCAL) {
|
||||||
requireGlobalInitializer = true
|
requireGlobalInitializer = true
|
||||||
} else {
|
} else {
|
||||||
requireThreadLocalInitializer = true // Either marked with thread local or only main thread visible.
|
requireThreadLocalInitializer = true // Either marked with thread local or only main thread visible.
|
||||||
|
|||||||
+2
-2
@@ -640,7 +640,7 @@ internal object DataFlowIR {
|
|||||||
val placeToFunctionsTable = !isAbstract && it !is IrConstructor && irClass != null
|
val placeToFunctionsTable = !isAbstract && it !is IrConstructor && irClass != null
|
||||||
&& (it.isOverridableOrOverrides || bridgeTarget != null || function.isSpecial || !irClass.isFinal())
|
&& (it.isOverridableOrOverrides || bridgeTarget != null || function.isSpecial || !irClass.isFinal())
|
||||||
val symbolTableIndex = if (placeToFunctionsTable) module.numberOfFunctions++ else -1
|
val symbolTableIndex = if (placeToFunctionsTable) module.numberOfFunctions++ else -1
|
||||||
val frozen = it is IrConstructor && irClass!!.annotations.findAnnotation(KonanFqNames.frozen) != null
|
val frozen = it is IrConstructor && irClass!!.isFrozen(context)
|
||||||
val functionSymbol = if (it.isExported())
|
val functionSymbol = if (it.isExported())
|
||||||
FunctionSymbol.Public(name.localHash.value, module, symbolTableIndex, attributes, it, bridgeTargetSymbol, takeName { name })
|
FunctionSymbol.Public(name.localHash.value, module, symbolTableIndex, attributes, it, bridgeTargetSymbol, takeName { name })
|
||||||
else
|
else
|
||||||
@@ -683,4 +683,4 @@ internal object DataFlowIR {
|
|||||||
return symbol
|
return symbol
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ fun test3(workers: Array<Worker>) {
|
|||||||
assertEquals(seen.size, workers.size)
|
assertEquals(seen.size, workers.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun test4() {
|
fun test4LegacyMM() {
|
||||||
assertFailsWith<InvalidMutabilityException> {
|
assertFailsWith<InvalidMutabilityException> {
|
||||||
AtomicReference(Data(1))
|
AtomicReference(Data(1))
|
||||||
}
|
}
|
||||||
@@ -83,7 +83,25 @@ fun test4() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun test5() {
|
fun test4() {
|
||||||
|
run {
|
||||||
|
val ref = AtomicReference(Data(1))
|
||||||
|
assertEquals(1, ref.value.value)
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val ref = AtomicReference<Data?>(null)
|
||||||
|
ref.compareAndSwap(null, Data(2))
|
||||||
|
assertEquals(2, ref.value!!.value)
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val ref = AtomicReference<Data?>(null).freeze()
|
||||||
|
assertFailsWith<InvalidMutabilityException> {
|
||||||
|
ref.compareAndSwap(null, Data(2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test5LegacyMM() {
|
||||||
assertFailsWith<InvalidMutabilityException> {
|
assertFailsWith<InvalidMutabilityException> {
|
||||||
AtomicReference<Data?>(null).value = Data(2)
|
AtomicReference<Data?>(null).value = Data(2)
|
||||||
}
|
}
|
||||||
@@ -94,6 +112,14 @@ fun test5() {
|
|||||||
assertEquals(3, ref.value!!.value)
|
assertEquals(3, ref.value!!.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun test5() {
|
||||||
|
val ref = AtomicReference<Data?>(null)
|
||||||
|
ref.value = Data(2)
|
||||||
|
assertEquals(2, ref.value!!.value)
|
||||||
|
ref.value = Data(3).freeze()
|
||||||
|
assertEquals(3, ref.value!!.value)
|
||||||
|
}
|
||||||
|
|
||||||
fun test6() {
|
fun test6() {
|
||||||
val int = AtomicInt(0)
|
val int = AtomicInt(0)
|
||||||
int.value = 239
|
int.value = 239
|
||||||
@@ -128,8 +154,13 @@ fun test7() {
|
|||||||
test1(workers)
|
test1(workers)
|
||||||
test2(workers)
|
test2(workers)
|
||||||
test3(workers)
|
test3(workers)
|
||||||
test4()
|
if (Platform.memoryModel == MemoryModel.EXPERIMENTAL) {
|
||||||
test5()
|
test4()
|
||||||
|
test5()
|
||||||
|
} else {
|
||||||
|
test4LegacyMM()
|
||||||
|
test5LegacyMM()
|
||||||
|
}
|
||||||
test6()
|
test6()
|
||||||
test7()
|
test7()
|
||||||
|
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ private fun debugString(value: Any?): String {
|
|||||||
* Otherwise memory leak could happen. To detect such leaks [kotlin.native.internal.GC.detectCycles]
|
* Otherwise memory leak could happen. To detect such leaks [kotlin.native.internal.GC.detectCycles]
|
||||||
* in debug mode could be helpful.
|
* in debug mode could be helpful.
|
||||||
*/
|
*/
|
||||||
@Frozen
|
@FrozenLegacyMM
|
||||||
@LeakDetectorCandidate
|
@LeakDetectorCandidate
|
||||||
@NoReorderFields
|
@NoReorderFields
|
||||||
public class AtomicReference<T> {
|
public class AtomicReference<T> {
|
||||||
@@ -232,7 +232,9 @@ public class AtomicReference<T> {
|
|||||||
* @throws InvalidMutabilityException if reference is not frozen.
|
* @throws InvalidMutabilityException if reference is not frozen.
|
||||||
*/
|
*/
|
||||||
constructor(value: T) {
|
constructor(value: T) {
|
||||||
checkIfFrozen(value)
|
if (this.isFrozen) {
|
||||||
|
checkIfFrozen(value)
|
||||||
|
}
|
||||||
value_ = value
|
value_ = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,13 @@ public annotation class ExportForCompiler
|
|||||||
@Retention(AnnotationRetention.BINARY)
|
@Retention(AnnotationRetention.BINARY)
|
||||||
internal annotation class Frozen
|
internal annotation class Frozen
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar to `@Frozen`, but works only for legacy MM. On the new MM this has no effect.
|
||||||
|
*/
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
@Retention(AnnotationRetention.BINARY)
|
||||||
|
internal annotation class FrozenLegacyMM
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fields of annotated class won't be sorted.
|
* Fields of annotated class won't be sorted.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user