atomicfu-compiler-plugin: support declaration of properties in objects for JVM IR

Fix: https://github.com/Kotlin/kotlinx-atomicfu/issues/241
Merge-request: KT-MR-6871
Merged-by: Maria Sokolova <maria.sokolova@jetbrains.com>
This commit is contained in:
mvicsokolova
2022-08-17 10:16:32 +00:00
committed by Space Team
parent 28dea9d949
commit cb23dbb492
8 changed files with 254 additions and 37 deletions
@@ -331,7 +331,11 @@ internal fun IrPluginContext.addDefaultGetter(property: IrProperty, parentClass:
visibility = property.visibility
returnType = field.type
}.apply {
dispatchReceiverParameter = (parentClass as? IrClass)?.thisReceiver?.deepCopyWithSymbols(this)
dispatchReceiverParameter = if (parentClass is IrClass && parentClass.kind == ClassKind.OBJECT) {
null
} else {
(parentClass as? IrClass)?.thisReceiver?.deepCopyWithSymbols(this)
}
body = factory.createBlockBody(
UNDEFINED_OFFSET, UNDEFINED_OFFSET, listOf(
IrReturnImpl(
@@ -7,7 +7,7 @@ package org.jetbrains.kotlinx.atomicfu.compiler.backend.jvm
import org.jetbrains.kotlin.backend.common.extensions.*
import org.jetbrains.kotlin.backend.common.lower.parents
import org.jetbrains.kotlin.backend.jvm.ir.*
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.ir.*
import org.jetbrains.kotlin.ir.builders.declarations.*
import org.jetbrains.kotlin.ir.builders.*
@@ -102,13 +102,13 @@ class AtomicfuJvmIrTransformer(
}
private fun IrProperty.transformAtomicfuProperty(parent: IrDeclarationContainer) {
val isTopLevel = parent is IrFile
val isTopLevel = parent is IrFile || (parent is IrClass && parent.kind == ClassKind.OBJECT)
when {
isAtomic() -> {
if (isTopLevel) {
val parentClass = generateWrapperClass(this, parent)
transformAtomicProperty(parentClass)
moveFromFileToClass(parent as IrFile, parentClass)
moveFromFileToClass(parent, parentClass)
} else {
transformAtomicProperty(parent as IrClass)
}
@@ -121,7 +121,7 @@ class AtomicfuJvmIrTransformer(
}
private fun IrProperty.moveFromFileToClass(
parentFile: IrFile,
parentFile: IrDeclarationContainer,
parentClass: IrClass
) {
parentFile.declarations.remove(this)
@@ -239,9 +239,9 @@ class AtomicfuJvmIrTransformer(
}
private fun buildVolatileRawField(property: IrProperty, parent: IrDeclarationContainer): IrField =
// Generate a new backing field for the given property:
// a volatile variable of the atomic value type
// val a = atomic(0)
// Generate a new backing field for the given property:
// a volatile variable of the atomic value type
// val a = atomic(0)
// volatile var a: Int = 0
property.backingField?.let { backingField ->
val init = backingField.initializer?.expression
@@ -271,9 +271,9 @@ class AtomicfuJvmIrTransformer(
} ?: error("Backing field of the atomic property ${property.render()} is null")
private fun addJucaAFUProperty(atomicProperty: IrProperty, parentClass: IrClass): IrProperty =
// Generate an atomic field updater for the volatile backing field of the given property:
// val a = atomic(0)
// volatile var a: Int = 0
// Generate an atomic field updater for the volatile backing field of the given property:
// val a = atomic(0)
// volatile var a: Int = 0
// val a$FU = AtomicIntegerFieldUpdater.newUpdater(parentClass, "a")
atomicProperty.backingField?.let { volatileField ->
val fuClass = atomicSymbols.getJucaAFUClass(volatileField.type)
@@ -332,14 +332,15 @@ class AtomicfuJvmIrTransformer(
}
} ?: error("Atomic property does not have backingField")
private fun generateWrapperClass(atomicProperty: IrProperty, parentFile: IrDeclarationContainer): IrClass {
private fun generateWrapperClass(atomicProperty: IrProperty, parentContainer: IrDeclarationContainer): IrClass {
val wrapperClassName = getVolatileWrapperClassName(atomicProperty)
val volatileWrapperClass = parentFile.declarations.singleOrNull { it is IrClass && it.name.asString() == wrapperClassName }
?: atomicSymbols.buildClassWithPrimaryConstructor(wrapperClassName, parentFile)
val volatileWrapperClass = parentContainer.declarations.singleOrNull { it is IrClass && it.name.asString() == wrapperClassName }
?: atomicSymbols.buildClassWithPrimaryConstructor(wrapperClassName, parentContainer)
// add a static instance of the generated wrapper class to the parent container
return (volatileWrapperClass as IrClass).also {
context.addProperty(
field = context.buildClassInstance(it, parentFile),
parent = parentFile,
field = context.buildClassInstance(it, parentContainer),
parent = parentContainer,
visibility = atomicProperty.visibility,
isStatic = true
)
@@ -480,8 +481,8 @@ class AtomicfuJvmIrTransformer(
val receiver = if (it is IrTypeOperatorCallImpl) it.argument else it
if (receiver.type.isAtomicValueType()) {
val valueType = if (it is IrTypeOperatorCallImpl) {
// If receiverExpression is a cast `s as AtomicRef<String>`
// then valueType is the type argument of Atomic* class `String`
// If receiverExpression is a cast `s as AtomicRef<String>`
// then valueType is the type argument of Atomic* class `String`
(it.type as IrSimpleType).arguments[0] as IrSimpleType
} else {
receiver.type.atomicToValueType()
@@ -601,7 +602,8 @@ class AtomicfuJvmIrTransformer(
val parent = valueParameter.parent
if (data != null && data.isTransformedAtomicExtension() &&
parent is IrFunctionImpl && !parent.isTransformedAtomicExtension() &&
parent.origin != IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA) {
parent.origin != IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA
) {
val index = valueParameter.index
if (index < 0 && !valueParameter.type.isAtomicValueType()) {
// index == -1 for `this` parameter
@@ -654,20 +656,28 @@ class AtomicfuJvmIrTransformer(
val isArrayReceiver = receiver.isArrayElementGetter()
val getAtomicProperty = if (isArrayReceiver) receiver.dispatchReceiver as IrCall else receiver
val atomicProperty = getAtomicProperty.getCorrespondingProperty()
val dispatchReceiver = getAtomicProperty.dispatchReceiver
?: run {
val dispatchReceiver = getAtomicProperty.dispatchReceiver.let {
val isObjectReceiver = it?.type?.classOrNull?.owner?.kind == ClassKind.OBJECT
if (it == null || isObjectReceiver) {
if (getAtomicProperty.symbol.owner.returnType.isAtomicValueType()) {
// for top-level atomic properties get wrapper class instance as a parent
getProperty(getStaticVolatileWrapperInstance(atomicProperty), null)
} else null
}
} else if (isObjectReceiver && getAtomicProperty.symbol.owner.returnType.isAtomicArrayType()) {
it
}
else null
} else it
}
// atomic property is handled by the Atomic*FieldUpdater instance
// atomic array elementis handled by the Atomic*Array instance
// atomic array elements handled by the Atomic*Array instance
val atomicHandler = propertyToAtomicHandler[atomicProperty]
?: error("No atomic handler found for the atomic property ${atomicProperty.render()}")
return AtomicFieldInfo(
dispatchReceiver = dispatchReceiver,
atomicHandler = getProperty(atomicHandler, if (isArrayReceiver) dispatchReceiver else null)
atomicHandler = getProperty(
atomicHandler,
if (isArrayReceiver && dispatchReceiver?.type?.classOrNull?.owner?.kind != ClassKind.OBJECT) dispatchReceiver else null
)
)
}
receiver.isThisReceiver() -> {
@@ -693,8 +703,8 @@ class AtomicfuJvmIrTransformer(
}
private val IrDeclaration.parentDeclarationContainer: IrDeclarationContainer
get() = parents.filterIsInstance<IrDeclarationContainer>().firstOrNull() ?:
error("In the sequence of parents for ${this.render()} no IrDeclarationContainer was found")
get() = parents.filterIsInstance<IrDeclarationContainer>().firstOrNull()
?: error("In the sequence of parents for ${this.render()} no IrDeclarationContainer was found")
private val IrFunction.containingFunction: IrFunction
get() {
@@ -742,7 +752,8 @@ class AtomicfuJvmIrTransformer(
): IrSimpleFunction {
val parent = this
val mangledName = mangleFunctionName(functionName, isArrayReceiver)
val updaterType = if (isArrayReceiver) atomicSymbols.getAtomicArrayType(valueType) else atomicSymbols.getFieldUpdaterType(valueType)
val updaterType =
if (isArrayReceiver) atomicSymbols.getAtomicArrayType(valueType) else atomicSymbols.getFieldUpdaterType(valueType)
findDeclaration<IrSimpleFunction> {
it.name.asString() == mangledName && it.valueParameters[0].type == updaterType
}?.let { return it }
@@ -754,7 +765,10 @@ class AtomicfuJvmIrTransformer(
if (functionName == LOOP) {
if (isArrayReceiver) generateAtomicfuArrayLoop(valueType) else generateAtomicfuLoop(valueType)
} else {
if (isArrayReceiver) generateAtomicfuArrayUpdate(functionName, valueType) else generateAtomicfuUpdate(functionName, valueType)
if (isArrayReceiver) generateAtomicfuArrayUpdate(functionName, valueType) else generateAtomicfuUpdate(
functionName,
valueType
)
}
this.parent = parent
parent.declarations.add(this)
@@ -765,9 +779,9 @@ class AtomicfuJvmIrTransformer(
declaration: IrSimpleFunction,
isArrayReceiver: Boolean
): IrSimpleFunction = findDeclaration {
it.name.asString() == mangleFunctionName(declaration.name.asString(), isArrayReceiver) &&
it.isTransformedAtomicExtension()
} ?: error("Could not find corresponding transformed declaration for the atomic extension ${declaration.render()}")
it.name.asString() == mangleFunctionName(declaration.name.asString(), isArrayReceiver) &&
it.isTransformedAtomicExtension()
} ?: error("Could not find corresponding transformed declaration for the atomic extension ${declaration.render()}")
private fun IrSimpleFunction.generateAtomicfuLoop(valueType: IrType) {
addValueParameter(ATOMIC_HANDLER, atomicSymbols.getFieldUpdaterType(valueType))
@@ -813,11 +827,12 @@ class AtomicfuJvmIrTransformer(
}
private fun getStaticVolatileWrapperInstance(atomicProperty: IrProperty): IrProperty {
val refVolatileStaticPropertyName = getVolatileWrapperClassName(atomicProperty).replaceFirstChar { it.lowercaseChar() }
return atomicProperty.fileParent.declarations.singleOrNull {
it is IrProperty && it.name.asString() == refVolatileStaticPropertyName
val volatileWrapperClass = atomicProperty.parent as IrClass
return (volatileWrapperClass.parent as IrDeclarationContainer).declarations.singleOrNull {
it is IrProperty && it.backingField != null &&
it.backingField!!.type.classOrNull == volatileWrapperClass.symbol
} as? IrProperty
?: error("Static instance of $refVolatileStaticPropertyName is missing in the file ${atomicProperty.fileParent.render()}")
?: error("Static instance of ${volatileWrapperClass.name.asString()} is missing in ${volatileWrapperClass.parent}")
}
private fun IrType.isKotlinxAtomicfuPackage() =
@@ -873,7 +888,9 @@ class AtomicfuJvmIrTransformer(
symbol.owner.dispatchReceiverParameter?.type?.isTraceBaseType() == true
private fun getVolatileWrapperClassName(property: IrProperty) =
property.name.asString().capitalizeAsciiOnly() + '$' + property.fileParent.name.substringBefore('.') + VOLATILE_WRAPPER_SUFFIX
property.name.asString().capitalizeAsciiOnly() + '$' +
(if (property.parent is IrFile) (property.parent as IrFile).name else property.parent.kotlinFqName.asString()).substringBefore('.') +
VOLATILE_WRAPPER_SUFFIX
private fun mangleFunctionName(name: String, isArrayReceiver: Boolean) =
if (isArrayReceiver) "$name$$ATOMICFU$ATOMIC_ARRAY_RECEIVER_SUFFIX" else "$name$$ATOMICFU"
@@ -73,6 +73,12 @@ public class AtomicfuJsIrTestGenerated extends AbstractAtomicfuJsIrTest {
runTest("plugins/atomicfu/atomicfu-compiler/testData/box/ExtensionsTest.kt");
}
@Test
@TestMetadata("FieldInObjectTest.kt")
public void testFieldInObjectTest() throws Exception {
runTest("plugins/atomicfu/atomicfu-compiler/testData/box/FieldInObjectTest.kt");
}
@Test
@TestMetadata("IndexArrayElementGetterTest.kt")
public void testIndexArrayElementGetterTest() throws Exception {
@@ -73,6 +73,12 @@ public class AtomicfuJvmIrTestGenerated extends AbstractAtomicfuJvmIrTest {
runTest("plugins/atomicfu/atomicfu-compiler/testData/box/ExtensionsTest.kt");
}
@Test
@TestMetadata("FieldInObjectTest.kt")
public void testFieldInObjectTest() throws Exception {
runTest("plugins/atomicfu/atomicfu-compiler/testData/box/FieldInObjectTest.kt");
}
@Test
@TestMetadata("IndexArrayElementGetterTest.kt")
public void testIndexArrayElementGetterTest() throws Exception {
@@ -5,6 +5,7 @@ public final class DelegatedProperties$A {
public method <init>(@org.jetbrains.annotations.NotNull p0: DelegatedProperties$B): void
public final @org.jetbrains.annotations.NotNull method getB(): DelegatedProperties$B
public final inner class DelegatedProperties$A
public final inner class DelegatedProperties$B
}
@kotlin.Metadata
@@ -0,0 +1,67 @@
import kotlinx.atomicfu.*
import kotlin.test.*
import kotlin.random.*
object Provider {
val port = atomic(Random.nextInt(20, 90) * 100)
fun next(): Int = port.incrementAndGet()
private val _l = atomic(2424920024888888848)
fun getL() = _l.incrementAndGet()
val _ref = atomic<String?>(null)
val _x = atomic(false)
val intArr = AtomicIntArray(10)
val longArr = AtomicLongArray(10)
val refArr = atomicArrayOfNulls<Any?>(5)
}
object DelegatedProvider {
val _a = atomic(42)
var a: Int by _a
var vInt by atomic(77)
}
private fun testFieldInObject() {
val port = Provider.next()
assertEquals(port + 1, Provider.next())
assertEquals(2424920024888888849, Provider.getL())
Provider._ref.compareAndSet(null, "abc")
assertEquals("abc", Provider._ref.value)
assertFalse(Provider._x.value)
Provider.intArr[8].value = 454
assertEquals(455, Provider.intArr[8].incrementAndGet())
Provider.longArr[8].value = 4544096409680468
assertEquals(4544096409680470, Provider.longArr[8].addAndGet(2))
Provider.refArr[1].value = Provider._ref.value
assertEquals("abc", Provider.refArr[1].value)
}
private fun testDelegatedPropertiesInObject() {
assertEquals(42, DelegatedProvider.a)
DelegatedProvider._a.compareAndSet(42, 56)
assertEquals(56, DelegatedProvider.a)
DelegatedProvider.a = 77
DelegatedProvider._a.compareAndSet(77, 66)
assertEquals(66, DelegatedProvider._a.value)
assertEquals(66, DelegatedProvider.a)
assertEquals(77, DelegatedProvider.vInt)
DelegatedProvider.vInt = 55
assertEquals(110, DelegatedProvider.vInt * 2)
}
fun box(): String {
testFieldInObject()
testDelegatedPropertiesInObject()
return "OK"
}
@@ -0,0 +1,115 @@
@kotlin.Metadata
public final class DelegatedProvider$_a$DelegatedProvider$VolatileWrapper {
// source: 'FieldInObjectTest.kt'
private final static @org.jetbrains.annotations.NotNull field _a$FU: java.util.concurrent.atomic.AtomicIntegerFieldUpdater
private volatile @kotlin.jvm.Volatile field _a: int
static method <clinit>(): void
public method <init>(): void
public synthetic final static method access$get_a$p(p0: DelegatedProvider$_a$DelegatedProvider$VolatileWrapper): int
public synthetic final static method access$set_a$p(p0: DelegatedProvider$_a$DelegatedProvider$VolatileWrapper, p1: int): void
public final static @org.jetbrains.annotations.NotNull method get_a$FU(): java.util.concurrent.atomic.AtomicIntegerFieldUpdater
public final method get_a(): int
public final inner class DelegatedProvider$_a$DelegatedProvider$VolatileWrapper
}
@kotlin.Metadata
public final class DelegatedProvider {
// source: 'FieldInObjectTest.kt'
public final static @org.jetbrains.annotations.NotNull field INSTANCE: DelegatedProvider
public final static @org.jetbrains.annotations.NotNull field _a$DelegatedProvider$VolatileWrapper: DelegatedProvider$_a$DelegatedProvider$VolatileWrapper
private volatile @kotlin.jvm.Volatile field vInt: int
static method <clinit>(): void
private method <init>(): void
public final method getA(): int
public final method getVInt(): int
public final static @org.jetbrains.annotations.NotNull method get_a$DelegatedProvider$VolatileWrapper(): DelegatedProvider$_a$DelegatedProvider$VolatileWrapper
public final method setA(p0: int): void
public final method setVInt(p0: int): void
public final inner class DelegatedProvider$_a$DelegatedProvider$VolatileWrapper
}
@kotlin.Metadata
public final class FieldInObjectTestKt {
// source: 'FieldInObjectTest.kt'
public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String
private final static method testDelegatedPropertiesInObject(): void
private final static method testFieldInObject(): void
public final inner class DelegatedProvider$_a$DelegatedProvider$VolatileWrapper
public final inner class Provider$_ref$Provider$VolatileWrapper
public final inner class Provider$_x$Provider$VolatileWrapper
}
@kotlin.Metadata
public final class Provider$Port$Provider$VolatileWrapper {
// source: 'FieldInObjectTest.kt'
private final static @org.jetbrains.annotations.NotNull field port$FU: java.util.concurrent.atomic.AtomicIntegerFieldUpdater
private volatile @kotlin.jvm.Volatile field port: int
static method <clinit>(): void
public method <init>(): void
public final static @org.jetbrains.annotations.NotNull method getPort$FU(): java.util.concurrent.atomic.AtomicIntegerFieldUpdater
public final method getPort(): int
public final inner class Provider$Port$Provider$VolatileWrapper
public final inner class kotlin/random/Random$Default
}
@kotlin.Metadata
public final class Provider$_l$Provider$VolatileWrapper {
// source: 'FieldInObjectTest.kt'
private final static @org.jetbrains.annotations.NotNull field _l$FU: java.util.concurrent.atomic.AtomicLongFieldUpdater
private volatile @kotlin.jvm.Volatile field _l: long
static method <clinit>(): void
public method <init>(): void
public synthetic final static method access$get_l$FU$p(): java.util.concurrent.atomic.AtomicLongFieldUpdater
public final inner class Provider$_l$Provider$VolatileWrapper
}
@kotlin.Metadata
public final class Provider$_ref$Provider$VolatileWrapper {
// source: 'FieldInObjectTest.kt'
private final static @org.jetbrains.annotations.NotNull field _ref$FU: java.util.concurrent.atomic.AtomicReferenceFieldUpdater
private volatile @kotlin.jvm.Volatile @org.jetbrains.annotations.Nullable field _ref: java.lang.Object
static method <clinit>(): void
public method <init>(): void
public final static @org.jetbrains.annotations.NotNull method get_ref$FU(): java.util.concurrent.atomic.AtomicReferenceFieldUpdater
public final @org.jetbrains.annotations.Nullable method get_ref(): java.lang.Object
public final inner class Provider$_ref$Provider$VolatileWrapper
}
@kotlin.Metadata
public final class Provider$_x$Provider$VolatileWrapper {
// source: 'FieldInObjectTest.kt'
private final static @org.jetbrains.annotations.NotNull field _x$FU: java.util.concurrent.atomic.AtomicIntegerFieldUpdater
private volatile @kotlin.jvm.Volatile field _x: int
static method <clinit>(): void
public method <init>(): void
public final static @org.jetbrains.annotations.NotNull method get_x$FU(): java.util.concurrent.atomic.AtomicIntegerFieldUpdater
public final method get_x(): int
public final inner class Provider$_x$Provider$VolatileWrapper
}
@kotlin.Metadata
public final class Provider {
// source: 'FieldInObjectTest.kt'
public final static @org.jetbrains.annotations.NotNull field INSTANCE: Provider
public final static @org.jetbrains.annotations.NotNull field _l$Provider$VolatileWrapper: Provider$_l$Provider$VolatileWrapper
public final static @org.jetbrains.annotations.NotNull field _ref$Provider$VolatileWrapper: Provider$_ref$Provider$VolatileWrapper
public final static @org.jetbrains.annotations.NotNull field _x$Provider$VolatileWrapper: Provider$_x$Provider$VolatileWrapper
private final static @org.jetbrains.annotations.NotNull field intArr: java.util.concurrent.atomic.AtomicIntegerArray
private final static @org.jetbrains.annotations.NotNull field longArr: java.util.concurrent.atomic.AtomicLongArray
public final static @org.jetbrains.annotations.NotNull field port$Provider$VolatileWrapper: Provider$Port$Provider$VolatileWrapper
private final static @org.jetbrains.annotations.NotNull field refArr: java.util.concurrent.atomic.AtomicReferenceArray
static method <clinit>(): void
private method <init>(): void
public final static @org.jetbrains.annotations.NotNull method getIntArr(): java.util.concurrent.atomic.AtomicIntegerArray
public final method getL(): long
public final static @org.jetbrains.annotations.NotNull method getLongArr(): java.util.concurrent.atomic.AtomicLongArray
public final static @org.jetbrains.annotations.NotNull method getPort$Provider$VolatileWrapper(): Provider$Port$Provider$VolatileWrapper
public final static @org.jetbrains.annotations.NotNull method getRefArr(): java.util.concurrent.atomic.AtomicReferenceArray
public final static @org.jetbrains.annotations.NotNull method get_ref$Provider$VolatileWrapper(): Provider$_ref$Provider$VolatileWrapper
public final static @org.jetbrains.annotations.NotNull method get_x$Provider$VolatileWrapper(): Provider$_x$Provider$VolatileWrapper
public final method next(): int
public final inner class Provider$Port$Provider$VolatileWrapper
public final inner class Provider$_l$Provider$VolatileWrapper
public final inner class Provider$_ref$Provider$VolatileWrapper
public final inner class Provider$_x$Provider$VolatileWrapper
}
@@ -11,6 +11,7 @@ public abstract class InlineExtensionWithTypeParameterTest$Segment {
public final class InlineExtensionWithTypeParameterTest$SemaphoreSegment {
// source: 'InlineExtensionWithTypeParameterTest.kt'
public method <init>(p0: int): void
public abstract inner class InlineExtensionWithTypeParameterTest$Segment
public final inner class InlineExtensionWithTypeParameterTest$SemaphoreSegment
}