diff --git a/libraries/tools/atomicfu/src/common/kotlin/org/jetbrains/kotlinx/atomicfu/gradle/AtomicfuKotlinGradleSubplugin.kt b/libraries/tools/atomicfu/src/common/kotlin/org/jetbrains/kotlinx/atomicfu/gradle/AtomicfuKotlinGradleSubplugin.kt index afcdc9a4678..e1a66db59c2 100644 --- a/libraries/tools/atomicfu/src/common/kotlin/org/jetbrains/kotlinx/atomicfu/gradle/AtomicfuKotlinGradleSubplugin.kt +++ b/libraries/tools/atomicfu/src/common/kotlin/org/jetbrains/kotlinx/atomicfu/gradle/AtomicfuKotlinGradleSubplugin.kt @@ -37,7 +37,8 @@ class AtomicfuKotlinGradleSubplugin : val project = kotlinCompilation.target.project val config = project.extensions.getByType(AtomicfuKotlinGradleExtension::class.java) return (config.isJsIrTransformationEnabled && kotlinCompilation.target.isJs()) || - (config.isJvmIrTransformationEnabled && kotlinCompilation.target.isJvm()) + (config.isJvmIrTransformationEnabled && kotlinCompilation.target.isJvm()) || + (config.isNativeIrTransformationEnabled && kotlinCompilation.target.isNative()) } override fun applyToCompilation( @@ -48,6 +49,7 @@ class AtomicfuKotlinGradleSubplugin : open class AtomicfuKotlinGradleExtension { var isJsIrTransformationEnabled = false var isJvmIrTransformationEnabled = false + var isNativeIrTransformationEnabled = false } override fun getPluginArtifact(): SubpluginArtifact = @@ -58,4 +60,6 @@ class AtomicfuKotlinGradleSubplugin : private fun KotlinTarget.isJs() = platformType == KotlinPlatformType.js private fun KotlinTarget.isJvm() = platformType == KotlinPlatformType.jvm || platformType == KotlinPlatformType.androidJvm + + private fun KotlinTarget.isNative() = platformType == KotlinPlatformType.native // todo wasm? } diff --git a/native/native.tests/build.gradle.kts b/native/native.tests/build.gradle.kts index 9aead14f3d3..8de97cc55be 100644 --- a/native/native.tests/build.gradle.kts +++ b/native/native.tests/build.gradle.kts @@ -36,6 +36,8 @@ sourceSets { } } +testsJar {} + // Tasks that run different sorts of tests. Most frequent use case: running specific tests at TeamCity. val infrastructureTest = nativeTest("infrastructureTest", "infrastructure") val codegenBoxTest = nativeTest("codegenBoxTest", "codegen & !frontend-fir") diff --git a/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeTests.kt b/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeTests.kt index b70f1ad67d2..89dd85173e6 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeTests.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeTests.kt @@ -305,3 +305,6 @@ private fun frontendFir() = arrayOf( private fun debugger() = annotation(Tag::class.java, "debugger") private fun infrastructure() = annotation(Tag::class.java, "infrastructure") +private fun k1libContents() = annotation(Tag::class.java, "k1libContents") +private fun k2libContents() = annotation(Tag::class.java, "k2libContents") +private fun atomicfu() = annotation(Tag::class.java, "atomicfu") diff --git a/plugins/atomicfu/atomicfu-compiler/build.gradle.kts b/plugins/atomicfu/atomicfu-compiler/build.gradle.kts index 90c6cbe3e31..400647872c5 100644 --- a/plugins/atomicfu/atomicfu-compiler/build.gradle.kts +++ b/plugins/atomicfu/atomicfu-compiler/build.gradle.kts @@ -1,6 +1,9 @@ -import org.jetbrains.kotlin.gradle.targets.js.KotlinJsCompilerAttribute import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages +import org.jetbrains.kotlin.gradle.targets.js.KotlinJsCompilerAttribute +import org.jetbrains.kotlin.gradle.targets.js.d8.D8RootPlugin +import org.jetbrains.kotlin.konan.target.HostManager description = "Atomicfu Compiler Plugin" @@ -9,6 +12,11 @@ plugins { id("jps-compatible") } +project.configureJvmToolchain(JdkMajorVersion.JDK_11_0) + +// WARNING: Native target is host-dependent. Re-running the same build on another host OS may bring to a different result. +val nativeTargetName = HostManager.host.name + val antLauncherJar by configurations.creating val testJsRuntime by configurations.creating { attributes { @@ -26,6 +34,18 @@ val atomicfuJsClasspath by configurations.creating { val atomicfuJvmClasspath by configurations.creating +val atomicfuNativeKlib by configurations.creating { + attributes { + attribute(KotlinPlatformType.attribute, KotlinPlatformType.native) + // WARNING: Native target is host-dependent. Re-running the same build on another host OS may bring to a different result. + attribute(KotlinNativeTarget.konanTargetAttribute, nativeTargetName) + attribute(Usage.USAGE_ATTRIBUTE, objects.named(KotlinUsages.KOTLIN_API)) + attribute(KotlinPlatformType.attribute, KotlinPlatformType.native) + // todo: don't add platform specific attribute + attribute(KotlinNativeTarget.konanTargetAttribute, org.jetbrains.kotlin.konan.target.KonanTarget.MACOS_X64.toString()) + } +} + val atomicfuJsIrRuntimeForTests by configurations.creating { attributes { attribute(KotlinPlatformType.attribute, KotlinPlatformType.js) @@ -39,6 +59,7 @@ repositories { } dependencies { + testImplementation(project(mapOf("path" to ":native:native.tests"))) compileOnly(intellijCore()) compileOnly(commonDependency("org.jetbrains.intellij.deps:asm-all")) @@ -68,14 +89,31 @@ dependencies { testApi(commonDependency("junit:junit")) testApi(project(":kotlin-test:kotlin-test-jvm")) + // Dependencies for Kotlin/Native test infra: + testImplementation(projectTests(":native:native.tests")) + testImplementation(project(":native:kotlin-native-utils")) + testImplementation(commonDependency("org.jetbrains.teamcity:serviceMessages")) + + // todo: remove unnecessary dependencies + testImplementation(project(":kotlin-compiler-runner-unshaded")) + + testImplementation(commonDependency("commons-lang:commons-lang")) + testImplementation(projectTests(":compiler:tests-common")) + testImplementation(projectTests(":compiler:tests-common-new")) + testImplementation(projectTests(":compiler:test-infrastructure")) + testCompileOnly("org.jetbrains.kotlinx:atomicfu:0.17.1") // todo: do not hardcode atomicfu version + + testApiJUnit5() + testRuntimeOnly(kotlinStdlib()) testRuntimeOnly(project(":kotlin-preloader")) // it's required for ant tests testRuntimeOnly(project(":compiler:backend-common")) testRuntimeOnly(commonDependency("org.fusesource.jansi", "jansi")) atomicfuJsClasspath("org.jetbrains.kotlinx:atomicfu-js:0.17.1") { isTransitive = false } - atomicfuJvmClasspath("org.jetbrains.kotlinx:atomicfu:0.17.1") { isTransitive = false } atomicfuJsIrRuntimeForTests(project(":kotlinx-atomicfu-runtime")) { isTransitive = false } + atomicfuJvmClasspath("org.jetbrains.kotlinx:atomicfu:0.17.1") { isTransitive = false } + atomicfuNativeKlib("org.jetbrains.kotlinx:atomicfu:0.17.1") { isTransitive = false } embedded(project(":kotlinx-atomicfu-runtime")) { attributes { @@ -125,3 +163,11 @@ projectTest(jUnitMode = JUnitMode.JUnit5) { publish() standardPublicJars() + +val nativeBoxTest = nativeTest( + taskName = "nativeBoxTest", + tag = "atomicfu", + requirePlatformLibs = true, + customDependencies = listOf(atomicfuJvmClasspath), + customKlibDependencies = listOf(atomicfuNativeKlib) +) diff --git a/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/common/AbstractAtomicfuIrBuilder.kt b/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/common/AbstractAtomicfuIrBuilder.kt index 8798f7a4753..9fd69d7fec3 100644 --- a/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/common/AbstractAtomicfuIrBuilder.kt +++ b/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/common/AbstractAtomicfuIrBuilder.kt @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.ir.symbols.IrSymbol import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.defaultType import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl +import org.jetbrains.kotlin.ir.types.isBoolean import org.jetbrains.kotlin.ir.types.isInt import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.Name @@ -41,11 +42,13 @@ abstract class AbstractAtomicfuIrBuilder( } // atomicArr.get(index) - fun atomicGetArrayElement(atomicArrayClass: IrClassSymbol, receiver: IrExpression, index: IrExpression) = - irCall(atomicSymbols.getAtomicHandlerFunctionSymbol(atomicArrayClass, "get")).apply { + fun atomicGetArrayElement(atomicArrayClass: IrClassSymbol, returnType: IrType, receiver: IrExpression, index: IrExpression): IrCall { + val arrayElement = irCall(atomicSymbols.getAtomicHandlerFunctionSymbol(atomicArrayClass, "get")).apply { dispatchReceiver = receiver putValueArgument(0, index) } + return if (returnType.isBoolean() && arrayElement.type.isInt()) arrayElement.toBoolean() else arrayElement + } fun irCallWithArgs(symbol: IrSimpleFunctionSymbol, dispatchReceiver: IrExpression?, extensionReceiver: IrExpression?, valueArguments: List) = irCall(symbol).apply { @@ -157,7 +160,7 @@ abstract class AbstractAtomicfuIrBuilder( } fun IrExpression.toBoolean() = irNotEquals(this, irInt(0)) as IrCall - fun IrExpression.toInt() = irIfThenElse(irBuiltIns.intType, irEquals(this, irBoolean(true)), irInt(1), irInt(0)) + fun IrExpression.toInt() = irIfThenElse(irBuiltIns.intType, this, irInt(1), irInt(0)) fun irClassWithPrivateConstructor( name: String, @@ -293,7 +296,7 @@ abstract class AbstractAtomicfuIrBuilder( } abstract fun atomicfuLoopBody(valueType: IrType, valueParameters: List): IrBlockBody - abstract fun atomicfuArrayLoopBody(atomicArrayClass: IrClassSymbol, valueParameters: List): IrBlockBody + abstract fun atomicfuArrayLoopBody(atomicArrayClass: IrClassSymbol, valueType: IrType, valueParameters: List): IrBlockBody abstract fun atomicfuUpdateBody(functionName: String, valueType: IrType, valueParameters: List): IrBlockBody abstract fun atomicfuArrayUpdateBody(functionName: String, valueType: IrType, atomicArrayClass: IrClassSymbol, valueParameters: List): IrBlockBody } diff --git a/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/common/AbstractAtomicfuTransformer.kt b/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/common/AbstractAtomicfuTransformer.kt index 1ff382da7f0..f549adf278f 100644 --- a/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/common/AbstractAtomicfuTransformer.kt +++ b/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/common/AbstractAtomicfuTransformer.kt @@ -13,12 +13,9 @@ import org.jetbrains.kotlin.descriptors.DescriptorVisibility import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.backend.js.utils.valueArguments +import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter import org.jetbrains.kotlin.ir.builders.declarations.buildFun -import org.jetbrains.kotlin.ir.builders.irGetField -import org.jetbrains.kotlin.ir.builders.irSetField -import org.jetbrains.kotlin.ir.builders.irBlockBody -import org.jetbrains.kotlin.ir.builders.irReturn import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl import org.jetbrains.kotlin.ir.expressions.* @@ -51,27 +48,32 @@ abstract class AbstractAtomicfuTransformer(val pluginContext: IrPluginContext) { companion object { const val CONSTRAINTS_MESSAGE = - "\n\nPlease make sure that you follow these constraints for using atomic properties:\n" + - " * To ensure that atomic properties are not accessed out of the current Kotlin module, it is necessary to declare atomic properties as private or internal,\n" + - " or make the containing class private or internal.\n" + - " To expose the atomic property value to the public, use a delegated property declared in the same scope, e.g: \n" + + "\nPlease make sure that you follow these constraints for using atomic properties:\n" + + " * To ensure that atomic properties are not accessed out of the current Kotlin module, it is necessary to declare atomic properties as private or internal.\n" + + " Alternatively, you can make the containing class private or internal.\n" + + " If you need to expose the atomic property value to the public, consider using a delegated property declared within the same scope:\n" + " ```\n" + " private val _a = atomic(initial) \n" + " public var a: T by _a \n" + " ```\n" + - " * Only perform operations directly on atomic values:\n" + - " * Avoid storing references to atomic values in local variables, e.g.\n" + - " ```\n" + - " val top = atomic(null)\n" + - " top.compareAndSet(null, Node(1)) // OK: direct invocation on the atomic property is allowed \n" + - " ```\n" + - " ```\n" + - " val tmp = top\n" + - " tmp.compareAndSet(null, Node(1)) // DON'T DO THIS: invocation on a local variable is NOT allowed \n" + - " ```\n" + - " * Do not leak references to atomic values in other way (return, pass as params, etc).\n" + - " * Avoid introducing complex data flow within parameters of atomic operations:\n" + - " Instead of top.compareAndSet(cur, ) use \n" + + " * Directly invoke operations on atomic properties, like this:\n" + + " ```\n" + + " val top = atomic(null)\n" + + " top.compareAndSet(null, Node(1)) // OK\n" + + " ```\n" + + " * Refrain from invoking atomic operations on local variables:\n" + + " ```\n" + + " val top = atomic(null)\n" + + " val tmp = top\n" + + " tmp.compareAndSet(null, Node(1)) // DON'T DO THIS\n" + + " ```\n" + + " * Avoid leaking references to atomic values in other ways, such as returning them or passing them as parameters.\n" + + " * Be cautious with the complexity of data flow within parameters of atomic operations.\n" + + " For instance, instead of using intricate expression directly as an argument, e.g.:\n" + + " ```\n" + + " top.compareAndSet(cur, )\n" + + " ```\n" + + " create a separate variable to hold the complex expression's value and then perform the operation:\n" + " ```\n" + " val newValue = \n" + " top.compareAndSet(cur, newValue) \n" + @@ -223,7 +225,7 @@ abstract class AbstractAtomicfuTransformer(val pluginContext: IrPluginContext) { */ private fun IrDeclarationContainer.transformDelegatedAtomic(atomicProperty: IrProperty) { val getDelegate = atomicProperty.backingField?.initializer?.expression - require(getDelegate is IrCall) { "Unexpected initializer of the delegated property ${atomicProperty.render()}: " + + require(getDelegate is IrCall) { "Unexpected initializer of the delegated property ${atomicProperty.atomicfuRender()}: " + "expected invocation of the delegate atomic property getter, but found ${getDelegate?.render()}." + CONSTRAINTS_MESSAGE } val delegateVolatileField = when { getDelegate.isAtomicFactoryCall() -> { @@ -341,7 +343,7 @@ abstract class AbstractAtomicfuTransformer(val pluginContext: IrPluginContext) { parentContainer: IrDeclarationContainer ): IrField { val atomicArrayField = - requireNotNull(atomicProperty.backingField) { "The backing field of the atomic array ${atomicProperty.render()} should not be null." + CONSTRAINTS_MESSAGE } + requireNotNull(atomicProperty.backingField) { "The backing field of the atomic array [${atomicProperty.atomicfuRender()}] should not be null." + CONSTRAINTS_MESSAGE } val initializer = atomicArrayField.initializer?.expression if (initializer == null) { // replace field initialization in the init block @@ -362,7 +364,7 @@ abstract class AbstractAtomicfuTransformer(val pluginContext: IrPluginContext) { parentContainer ).also { val initExpr = it.initializer?.expression - ?: error("The generated atomic array field ${it.render()} should've already be initialized." + CONSTRAINTS_MESSAGE) + ?: error("The generated atomic array field [${it.render()}] should've already be initialized." + CONSTRAINTS_MESSAGE) it.initializer = null initBlock.updateFieldInitialization(atomicArrayField.symbol, it.symbol, initExpr, initExprIndex) } @@ -451,8 +453,8 @@ abstract class AbstractAtomicfuTransformer(val pluginContext: IrPluginContext) { (parent is IrClass && (parentAsClass.visibility == DescriptorVisibilities.PRIVATE || parentAsClass.visibility == DescriptorVisibilities.INTERNAL))) { "To ensure that atomic properties are not accessed out of the current Kotlin module, it is necessary to declare atomic properties as private or internal.\n" + - "Please consider declaring [${this.atomicfuRender()}] from [${this.parent.render()}] as a private or internal property." + - if (parent is IrClass) ",\nYou may also make the containing class [${parentAsClass.render()}] private or internal.\n" else "\n" + + "Please consider declaring [${this.atomicfuRender()}] from [${this.parent.render()}] as a private or internal property.\n" + + (if (parent is IrClass) "You may also make the containing class [${parentAsClass.render()}] private or internal.\n" else "") + "Alternatively, if you need to expose the atomic property value to the public, you can use a delegated property declared within the same scope, e.g:\n" + "```\n" + "private val _a = atomic(initial) \n" + @@ -554,7 +556,8 @@ abstract class AbstractAtomicfuTransformer(val pluginContext: IrPluginContext) { val isArrayReceiver = when { propertyGetterCall is IrCall -> propertyGetterCall.isArrayElementReceiver(data) propertyGetterCall.isThisReceiver() -> data != null && data.name.asString().isMangledAtomicArrayExtension() - else -> error("Unsupported atomic array receiver ${propertyGetterCall.render()}" + CONSTRAINTS_MESSAGE) + else -> error("Could not define the type of receiver of the function call ${expression.render()}: " + + "found function call receiver ${propertyGetterCall.render()}, parentFunction = ${data?.render()}." + CONSTRAINTS_MESSAGE) } if (expression.symbol.owner.isFromKotlinxAtomicfuPackage()) { /** @@ -692,14 +695,17 @@ abstract class AbstractAtomicfuTransformer(val pluginContext: IrPluginContext) { requireNotNull(parentFunction) { "Expected containing function of the call ${expression.render()}, but found null." + CONSTRAINTS_MESSAGE } val loopFunc = parentFunction.parentDeclarationContainer.getOrBuildInlineLoopFunction( functionName = functionName, - valueType = if (valueType.isBoolean()) irBuiltIns.intType else valueType, + valueType = if (valueType.isBoolean() && castBooleanFieldsToInt) irBuiltIns.intType else valueType, isArrayReceiver = isArrayReceiver ) // We have to copy this lambda, because it is passed to both foo$atomicfu and foo$atomicfu$array transformations, // and otherwise causes "lambda is already bound" error in K/N. val action = (expression.getValueArgument(0) as IrFunctionExpression).apply { function.body?.transform(this@AtomicFunctionCallTransformer, parentFunction) - if (function.valueParameters[0].type.isBoolean()) { + // In case of AtomicBoolean receiver on JVM the argument of the lambda is cast to Int, because the current value of the receiver is retrieved with the AtomicIntegerUpdater. + // AtomicBoolean.loop(action: (Boolean)-> Unit) -> action: (Int) -> Unit + // For native this is not necessary and the type is left unchanged. + if (function.valueParameters[0].type.isBoolean() && castBooleanFieldsToInt) { function.valueParameters[0].type = irBuiltIns.intType function.returnType = irBuiltIns.intType } @@ -835,7 +841,7 @@ abstract class AbstractAtomicfuTransformer(val pluginContext: IrPluginContext) { val isArrayReceiver = parentFunction.name.asString().isMangledAtomicArrayExtension() if (isArrayReceiver) parentFunction.valueParameters[0].capture() else parentFunction.valueParameters[1].capture() } - else -> error("Unexpected type of atomic function call receiver: ${atomicCallReceiver.render()} \n" + CONSTRAINTS_MESSAGE) + else -> error("Unexpected type of atomic function call receiver: ${atomicCallReceiver.render()}, parentFunction = ${parentFunction?.render()}." + CONSTRAINTS_MESSAGE) } override fun visitGetValue(expression: IrGetValue, data: IrFunction?): IrExpression { @@ -923,7 +929,7 @@ abstract class AbstractAtomicfuTransformer(val pluginContext: IrPluginContext) { addSyntheticValueParametersToTransformedAtomicExtension(true, valueType) addValueParameter(ACTION, atomicSymbols.function1Type(valueType, irBuiltIns.unitType)) body = with(atomicSymbols.createBuilder(symbol)) { - atomicfuArrayLoopBody(atomicfuArrayClass, valueParameters) + atomicfuArrayLoopBody(atomicfuArrayClass, valueType, valueParameters) } returnType = irBuiltIns.unitType } @@ -977,6 +983,10 @@ abstract class AbstractAtomicfuTransformer(val pluginContext: IrPluginContext) { } } + // If true, transforms AtomicBoolean properties to Int volatile property + updater. + // Tweaks types of value arguments where this property is used accordingly. + abstract val castBooleanFieldsToInt: Boolean + // Builds the signature of the transformed atomic extension. abstract fun buildTransformedAtomicExtensionSignature(atomicExtension: IrFunction, isArrayReceiver: Boolean): IrSimpleFunction // Adds value parameters for transformed inline extension functions. @@ -1110,7 +1120,7 @@ abstract class AbstractAtomicfuTransformer(val pluginContext: IrPluginContext) { require(index.name.asString() == INDEX && index.type == irBuiltIns.intType) index.capture() } - else -> error("Unexpected type of atomic receiver expression: ${this.render()} \n" + CONSTRAINTS_MESSAGE) + else -> error("Unexpected type of atomic array receiver: ${this.render()}, parentFunction = ${parentFunction?.render()}\n" + CONSTRAINTS_MESSAGE) } // A.kt -> A$VolatileWrapper$atomicfu diff --git a/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/jvm/AtomicfuJvmIrTransformer.kt b/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/jvm/AtomicfuJvmIrTransformer.kt index 8d10abc0087..d2572d52a52 100644 --- a/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/jvm/AtomicfuJvmIrTransformer.kt +++ b/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/jvm/AtomicfuJvmIrTransformer.kt @@ -98,7 +98,7 @@ class AtomicfuJvmIrTransformer( * } * } } */ - val volatileField = buildVolatileBackingField(from, parentClass, true) + val volatileField = buildVolatileBackingField(from, parentClass, castBooleanFieldsToInt) val volatileProperty = if (volatileField.parent == from.parent) { // The index is relevant only if the property belongs to the same class as the original atomic property (not the generated wrapper). parentClass.replacePropertyAtIndex(volatileField, DescriptorVisibilities.PRIVATE, isVar = true, isStatic = false, index) @@ -271,6 +271,12 @@ class AtomicfuJvmIrTransformer( } } + /** + * On JVM all Boolean fields are cast to Int, as they only can be updated via AtomicIntegerFieldUpdaters + */ + override val castBooleanFieldsToInt: Boolean + get() = true + /** * Builds the signature of the transformed atomic extension: * diff --git a/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/jvm/JvmAtomicfuIrBuilder.kt b/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/jvm/JvmAtomicfuIrBuilder.kt index a0f59a2f0bc..080e021fb79 100644 --- a/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/jvm/JvmAtomicfuIrBuilder.kt +++ b/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/jvm/JvmAtomicfuIrBuilder.kt @@ -112,7 +112,6 @@ class JvmAtomicfuIrBuilder internal constructor( } */ // dispatchReceiver: IrValueParameter, atomicHandler: IrValueParameter, action: IrValueParameter - // todo this should also be abstracted out override fun atomicfuLoopBody(valueType: IrType, valueParameters: List) = irBlockBody { val dispatchReceiver = valueParameters[0] @@ -141,7 +140,7 @@ class JvmAtomicfuIrBuilder internal constructor( } } */ - override fun atomicfuArrayLoopBody(atomicArrayClass: IrClassSymbol, valueParameters: List) = + override fun atomicfuArrayLoopBody(atomicArrayClass: IrClassSymbol, valueType: IrType, valueParameters: List) = irBlockBody { val atomicHandler = valueParameters[0] val index = valueParameters[1] @@ -150,7 +149,7 @@ class JvmAtomicfuIrBuilder internal constructor( condition = irTrue() body = irBlock { val cur = createTmpVariable( - atomicGetArrayElement(atomicArrayClass, irGet(atomicHandler), irGet(index)), + atomicGetArrayElement(atomicArrayClass, valueType, irGet(atomicHandler), irGet(index)), "atomicfu\$cur", false ) +irCall(atomicSymbols.invoke1Symbol).apply { @@ -270,7 +269,7 @@ class JvmAtomicfuIrBuilder internal constructor( condition = irTrue() body = irBlock { val cur = createTmpVariable( - atomicGetArrayElement(atomicArrayClass, irGet(atomicHandler), irGet(index)), + atomicGetArrayElement(atomicArrayClass, valueType, irGet(atomicHandler), irGet(index)), "atomicfu\$cur", false ) val upd = createTmpVariable( diff --git a/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/native/AtomicfuNativeIrTransformer.kt b/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/native/AtomicfuNativeIrTransformer.kt new file mode 100644 index 00000000000..76708b5d682 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/native/AtomicfuNativeIrTransformer.kt @@ -0,0 +1,338 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlinx.atomicfu.compiler.backend.native + +import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext +import org.jetbrains.kotlin.backend.jvm.ir.representativeUpperBound +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET +import org.jetbrains.kotlin.ir.backend.js.utils.valueArguments +import org.jetbrains.kotlin.ir.builders.declarations.* +import org.jetbrains.kotlin.ir.builders.irBlockBody +import org.jetbrains.kotlin.ir.builders.irCall +import org.jetbrains.kotlin.ir.builders.irReturn +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionExpressionImpl +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlinx.atomicfu.compiler.backend.common.AbstractAtomicSymbols +import org.jetbrains.kotlinx.atomicfu.compiler.backend.common.AbstractAtomicfuTransformer + +private const val ATOMICFU = "atomicfu" +private const val REF_GETTER = "refGetter\$$ATOMICFU" +private const val ATOMIC_ARRAY = "atomicArray\$$ATOMICFU" +private const val INDEX = "index\$$ATOMICFU" + +class AtomicfuNativeIrTransformer( + pluginContext: IrPluginContext, + override val atomicSymbols: NativeAtomicSymbols +) : AbstractAtomicfuTransformer(pluginContext) { + + override val atomicPropertiesTransformer: AtomicPropertiesTransformer + get() = NativeAtomicPropertiesTransformer() + + override val atomicFunctionsTransformer: AtomicFunctionCallTransformer + get() = NativeAtomicFunctionCallTransformer() + + private inner class NativeAtomicPropertiesTransformer : AtomicPropertiesTransformer() { + + override fun IrClass.addTransformedInClassAtomic(atomicProperty: IrProperty, index: Int): IrProperty = + addVolatileProperty(atomicProperty, index) + + override fun IrDeclarationContainer.addTransformedStaticAtomic(atomicProperty: IrProperty, index: Int): IrProperty = + addVolatileProperty(atomicProperty, index) + + private fun IrDeclarationContainer.addVolatileProperty(from: IrProperty, index: Int): IrProperty { + /** + * Atomic property [from] is replaced with the volatile field of the value type stored in [from] + * which is atomically updated via atomic intrinsics (see details about intrinsics in [kotlin.concurrent.AtomicInt]). + * + * Volatile field has the same visibility as the original atomic property. + * + * internal val a = atomic(0) --> internal @Volatile var a: Int = 0 + */ + val parentContainer = this + with(atomicSymbols.createBuilder(from.symbol)) { + val volatileField = buildVolatileBackingField(from, parentContainer, castBooleanFieldsToInt) + return parentContainer.replacePropertyAtIndex(volatileField, from.visibility, isVar = true, isStatic = from.parent is IrFile, index).also { + atomicfuPropertyToVolatile[from] = it + } + } + } + } + + private inner class NativeAtomicFunctionCallTransformer : AtomicFunctionCallTransformer() { + + override fun transformAtomicUpdateCallOnProperty( + expression: IrCall, + functionName: String, + valueType: IrType, + castType: IrType?, + getPropertyReceiver: IrExpression, + parentFunction: IrFunction? + ): IrExpression = + with(atomicSymbols.createBuilder(expression.symbol)) { + /** + * Atomic update call on the atomic property is replaced + * with the atomic intrinsic call on the corresponding volatile property reference: + * + * 1. Function call receiver is atomic property getter call. + * + * The call is replaced with atomic intrinsic call and the new receiver is + * the reference to the corresponding volatile property: + * + * internal val a = atomic(0) internal @Volatile var a$volatile = 0 + * ().compareAndSet(0, 5) ---> (this::a$volatile).compareAndSetField(0, 5) + * + * + * 2. Function is called in the body of the transformed atomic extension, + * the call receiver is the old receiver of the extension. + * + * The call is replaced with atomic intrinsic call and the new receiver is + * the invoked getter of the property reference that is the first parameter of the parent function: + * + * fun AtomicInt.foo(new: Int) { fun foo$atomicfu(crossinline refGetter: () -> KMutableProperty0, new: Int) { + * this.compareAndSet(value, new) ---> refGetter().compareAndSetField(refGetter().get(), new) + * } } + */ + val getPropertyReference = getVolatilePropertyReference(getPropertyReceiver, parentFunction) + return irCallAtomicNativeIntrinsic( + functionName = functionName, + propertyRef = getPropertyReference, + valueType = valueType, + returnType = expression.type, + valueArguments = expression.valueArguments + ) + } + + private fun NativeAtomicfuIrBuilder.getVolatilePropertyReference( + getPropertyReceiver: IrExpression, + parentFunction: IrFunction? + ): IrExpression = when { + getPropertyReceiver is IrCall -> { + /** + * Function call receiver is atomic property getter call. + * The new receiver is the reference to the corresponding volatile property: + * + * ().compareAndSet(0, 5) ---> (this::a$volatile).compareAndSetField(0, 5) + */ + val atomicProperty = getPropertyReceiver.getCorrespondingProperty() + val volatileProperty = atomicfuPropertyToVolatile[atomicProperty] + ?: error("No volatile property was generated for the atomic property ${atomicProperty.render()}") + irPropertyReference( + property = volatileProperty, + classReceiver = getPropertyReceiver.dispatchReceiver + ) + } + getPropertyReceiver.isThisReceiver() -> { + /** + * Function call receiver is the old receiver of the extension. + * The new receiver is the invoked getter of the property reference. + * that is the first parameter of the parent function: + * + * fun AtomicInt.foo(new: Int) { fun foo$atomicfu(refGetter: () -> KMutableProperty0, new: Int) { + * this.compareAndSet(value, new) ---> refGetter().compareAndSetField(refGetter().get(), new) + * } } + */ + require(parentFunction != null && parentFunction.isTransformedAtomicExtension()) + val refGetter = parentFunction.valueParameters[0] + require(refGetter.name.asString() == REF_GETTER && refGetter.type.classOrNull == irBuiltIns.functionN(0).symbol) + irCall(atomicSymbols.invoke0Symbol).apply { dispatchReceiver = refGetter.capture() } + } + else -> error("Unsupported type of atomic receiver expression: ${getPropertyReceiver.render()}") + } + + override fun buildSyntheticValueArgsForTransformedAtomicExtensionCall( + expression: IrCall, + getPropertyReceiver: IrExpression, + isArrayReceiver: Boolean, + parentFunction: IrFunction + ): List { + /** + * 1. Receiver of the atomic extension call is atomic property getter call. + * For atomic property receiver: generate getter lambda for the property reference corresponding to the call receiver + * + * inline fun AtomicInt.foo(arg: Int) {..} inline foo$atomicfu(refGetter: () -> KMutableProperty0, arg: Int) + * aClass._a.foo(arg) --> foo$atomicfu({_ -> aClass::_a}, arg) + * + * For array property receiver: pass atomic array and the index of the element as an argument + * + * inline fun AtomicInt.foo(arg: Int) {..} inline foo$atomicfu$array(array: AtomicIntArray, index: Int, arg: Int) + * aClass.intArr[7].foo(arg) --> foo$atomicfu$array(intArr, 7, arg) + * + * 2. Atomic extension is invoked in the body of the transformed atomic extension, + * the call receiver is the old receiver of the extension: + * get the corresponding argument from the parent function. + * + * fun AtomicInt.bar() {..} fun bar$atomicfu(refGetter: () -> KMutableProperty0) { .. } + * + * fun AtomicInt.foo(new: Int) { fun foo$atomicfu(refGetter: () -> KMutableProperty0, new: Int) { + * this.bar() ---> bar$atomicfu(refGetter) + * } } + */ + with(atomicSymbols.createBuilder(expression.symbol)) { + return if (isArrayReceiver) { + val index = getPropertyReceiver.getArrayElementIndex(parentFunction) + val atomicArray = getAtomicHandler(getPropertyReceiver, parentFunction) + listOf(atomicArray, index) + } else { + listOf(buildVolatilePropertyRefGetter(getPropertyReceiver, parentFunction)) + } + } + } + + // This counter is used to ensure uniqueness of functions for refGetter lambdas, + // as several functions with the same name may be created in the same scope + private var refGetterCounter: Int = 0 + + private fun NativeAtomicfuIrBuilder.buildVolatilePropertyRefGetter( + getPropertyReceiver: IrExpression, + parentFunction: IrFunction + ): IrExpression { + when { + getPropertyReceiver is IrCall -> { + /** + * Receiver of the atomic extension call is atomic property getter call. + * Generate inline getter lambda of the corresponding volatile property + * to pass as an argument to the transformed extension call: + * + * ().foo(5) ---> foo$atomicfu({_ -> this::a$volatile}, 5) + */ + val atomicProperty = getPropertyReceiver.getCorrespondingProperty() + val volatileProperty = atomicfuPropertyToVolatile[atomicProperty] + ?: error("No volatile property was generated for the atomic property ${atomicProperty.render()}") + val valueType = volatileProperty.backingField!!.type + return IrFunctionExpressionImpl( + UNDEFINED_OFFSET, UNDEFINED_OFFSET, + type = atomicSymbols.kMutableProperty0GetterType(valueType), + function = irBuiltIns.irFactory.buildFun { + name = Name.identifier("<${volatileProperty.name.asString()}-getter-${refGetterCounter++}>") + origin = AbstractAtomicSymbols.ATOMICFU_GENERATED_FUNCTION + returnType = atomicSymbols.kMutableProperty0Type(valueType) + isInline = true + visibility = DescriptorVisibilities.LOCAL + }.apply { + val lambda = this + body = irBlockBody { + +irReturn( + irPropertyReference(volatileProperty, getPropertyReceiver.dispatchReceiver) + ).apply { + type = irBuiltIns.nothingType + returnTargetSymbol = lambda.symbol + } + } + parent = parentFunction + }, + origin = IrStatementOrigin.LAMBDA + ) + } + getPropertyReceiver.isThisReceiver() -> { + /** + * Atomic extension is invoked in the body of the transformed atomic extension, + * the call receiver is the old receiver of the extension. + * Pass the parameter of parent extension as an argument: + * + * fun AtomicInt.foo(new: Int) { fun foo$atomicfu(refGetter: () -> KMutableProperty0, new: Int) { + * this.bar() ---> bar$atomicfu(refGetter) + * } } + */ + require(parentFunction.isTransformedAtomicExtension()) + val refGetter = parentFunction.valueParameters[0] + require(refGetter.name.asString() == REF_GETTER && refGetter.type.classOrNull == irBuiltIns.functionN(0).symbol) + return refGetter.capture() + } + else -> error("Unsupported type of atomic receiver expression: ${getPropertyReceiver.render()}") + } + } + + override fun IrValueParameter.remapValueParameter(transformedExtension: IrFunction): IrValueParameter? { + if (index < 0 && !type.isAtomicValueType()) { + // data is a transformed function + // index == -1 for `this` parameter + return transformedExtension.dispatchReceiverParameter + ?: error("Dispatch receiver of ${transformedExtension.render()} is null" + CONSTRAINTS_MESSAGE) + } + if (index >= 0) { + val shift = if (transformedExtension.name.asString().isMangledAtomicArrayExtension()) 2 else 1 + return transformedExtension.valueParameters[index + shift] + } + return null + } + + override fun IrExpression.isArrayElementReceiver(parentFunction: IrFunction?): Boolean { + return if (this is IrCall) this.isArrayElementGetter() else false + } + + override fun IrFunction.checkSyntheticArrayElementExtensionParameter(): Boolean { + if (valueParameters.size < 2) return false + return valueParameters[0].name.asString() == ATOMIC_ARRAY && atomicSymbols.isAtomicArrayHandlerType(valueParameters[0].type) && + valueParameters[1].name.asString() == INDEX && valueParameters[1].type == irBuiltIns.intType + } + + override fun IrFunction.checkSyntheticAtomicExtensionParameters(): Boolean { + if (valueParameters.isEmpty()) return false + return valueParameters[0].name.asString() == REF_GETTER && valueParameters[0].type.classOrNull == irBuiltIns.functionN(0).symbol + } + + override fun IrFunction.checkSyntheticParameterTypes(isArrayReceiver: Boolean, receiverValueType: IrType): Boolean { + if (isArrayReceiver) { + if (valueParameters.size < 2) return false + val atomicArrayClassSymbol = atomicSymbols.getAtomicArrayClassByValueType(receiverValueType) + return valueParameters[0].name.asString() == ATOMIC_ARRAY && valueParameters[0].type.classOrNull == atomicArrayClassSymbol && + valueParameters[1].name.asString() == INDEX && valueParameters[1].type == irBuiltIns.intType + } else { + if (valueParameters.size < 1) return false + return valueParameters[0].name.asString() == REF_GETTER && valueParameters[0].type.classOrNull == irBuiltIns.functionN(0).symbol + } + } + } + + /** + * On Native AtomicBoolean fields are transformed into Boolean volatile properties and updated via intrinsics. + */ + override val castBooleanFieldsToInt: Boolean + get() = false + + /** + * Builds the signature of the transformed atomic extension: + * + * inline fun AtomicInt.foo(arg: Int) --> inline fun foo$atomicfu(refGetter: () -> KMutableProperty0, arg': Int) + * inline fun foo$atomicfu$array(atomicArray: AtomicIntegerArray, index: Int, arg': Int) + */ + override fun buildTransformedAtomicExtensionSignature(atomicExtension: IrFunction, isArrayReceiver: Boolean): IrSimpleFunction { + val mangledName = mangleAtomicExtensionName(atomicExtension.name.asString(), isArrayReceiver) + val atomicReceiverType = atomicExtension.extensionReceiverParameter!!.type + val valueType = (atomicReceiverType as IrSimpleType).atomicToPrimitiveType() + return pluginContext.irFactory.buildFun { + name = Name.identifier(mangledName) + isInline = true + visibility = atomicExtension.visibility + origin = AbstractAtomicSymbols.ATOMICFU_GENERATED_FUNCTION + }.apply { + extensionReceiverParameter = null + dispatchReceiverParameter = atomicExtension.dispatchReceiverParameter?.deepCopyWithSymbols(this) + atomicExtension.typeParameters.forEach { addTypeParameter(it.name.asString(), it.representativeUpperBound) } + addSyntheticValueParametersToTransformedAtomicExtension(isArrayReceiver, if (valueType == irBuiltIns.anyNType) atomicReceiverType.arguments.first().typeOrNull!! else valueType) + atomicExtension.valueParameters.forEach { addValueParameter(it.name, it.type) } + returnType = atomicExtension.returnType + this.parent = atomicExtension.parent + } + } + + /** + * Adds synthetic value parameters to the transformed atomic extension (custom atomic extension or atomicfu inline update functions). + */ + override fun IrFunction.addSyntheticValueParametersToTransformedAtomicExtension(isArrayReceiver: Boolean, valueType: IrType) { + if (!isArrayReceiver) { + addValueParameter(REF_GETTER, atomicSymbols.kMutableProperty0GetterType(valueType)).apply { isCrossinline = true } + } else { + addValueParameter(ATOMIC_ARRAY, atomicSymbols.getParameterizedAtomicArrayType(valueType)) + addValueParameter(INDEX, irBuiltIns.intType) + } + } +} diff --git a/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/native/NativeAtomicSymbols.kt b/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/native/NativeAtomicSymbols.kt new file mode 100644 index 00000000000..905405cd431 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/native/NativeAtomicSymbols.kt @@ -0,0 +1,107 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlinx.atomicfu.compiler.backend.native + +import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext +import org.jetbrains.kotlin.backend.jvm.functionByName +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.jetbrains.kotlin.ir.symbols.IrClassSymbol +import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol +import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol +import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.constructors +import org.jetbrains.kotlin.ir.util.render +import org.jetbrains.kotlin.name.* +import org.jetbrains.kotlinx.atomicfu.compiler.backend.common.AbstractAtomicSymbols +import org.jetbrains.kotlinx.atomicfu.compiler.backend.js.referenceFunction + +class NativeAtomicSymbols( + context: IrPluginContext, + moduleFragment: IrModuleFragment +) : AbstractAtomicSymbols(context, moduleFragment) { + // kotlin.concurrent.Volatile annotation class + override val volatileAnnotationClass: IrClass + get() = context.referenceClass(ClassId(FqName("kotlin.concurrent"), Name.identifier("Volatile")))?.owner + ?: error("kotlin.concurrent.Volatile class is not found") + + // kotlin.concurrent.AtomicIntArray + override val atomicIntArrayClassSymbol: IrClassSymbol + get() = context.referenceClass(ClassId(FqName("kotlin.concurrent"), Name.identifier("AtomicIntArray"))) + ?: error("kotlin.concurrent.AtomicIntArray is not found") + + // kotlin.concurrent.AtomicLongArray + override val atomicLongArrayClassSymbol: IrClassSymbol + get() = context.referenceClass(ClassId(FqName("kotlin.concurrent"), Name.identifier("AtomicLongArray"))) + ?: error("kotlin.concurrent.AtomicLongArray is not found") + + // kotlin.concurrent.AtomicArray + override val atomicRefArrayClassSymbol: IrClassSymbol + get() = context.referenceClass(ClassId(FqName("kotlin.concurrent"), Name.identifier("AtomicArray"))) + ?: error("kotlin.concurrent.AtomicArray is not found") + + override fun getAtomicArrayConstructor(atomicArrayClassSymbol: IrClassSymbol): IrFunctionSymbol = + when (atomicArrayClassSymbol) { + atomicIntArrayClassSymbol, atomicLongArrayClassSymbol -> { + atomicArrayClassSymbol.owner.constructors.singleOrNull { + it.valueParameters.size == 1 && it.valueParameters[0].type == irBuiltIns.intType + }?.symbol ?: error("No `public constructor(size: Int) {}` was found for ${atomicArrayClassSymbol.owner.render()}") + } + atomicRefArrayClassSymbol -> { + context.referenceFunctions(CallableId(FqName("kotlin.concurrent"), Name.identifier("AtomicArray"))).singleOrNull() + ?: error("No factory function `public inline fun AtomicArray(size: Int, noinline init: (Int) -> T)` was found for ${atomicArrayClassSymbol.owner.render()}") + } + else -> error("Unsupported atomic array class found: ${atomicArrayClassSymbol.owner.render()}") + } + + // Intrinsics for atomic update of volatile properties + + val atomicGetFieldIntrinsic = + context.referenceFunctions(CallableId(FqName("kotlin.concurrent"), Name.identifier("atomicGetField"))).single() + + val atomicSetFieldIntrinsic = + context.referenceFunctions(CallableId(FqName("kotlin.concurrent"), Name.identifier("atomicSetField"))).single() + + val compareAndSetFieldIntrinsic = + context.referenceFunctions(CallableId(FqName("kotlin.concurrent"), Name.identifier("compareAndSetField"))).single() + + val getAndSetFieldIntrinsic = + context.referenceFunctions(CallableId(FqName("kotlin.concurrent"), Name.identifier("getAndSetField"))).single() + + val getAndAddIntFieldIntrinsic = + context.referenceFunctions(CallableId(FqName("kotlin.concurrent"), Name.identifier("getAndAddField"))) + .single { it.owner.returnType.isInt() } + + val getAndAddLongFieldIntrinsic = + context.referenceFunctions(CallableId(FqName("kotlin.concurrent"), Name.identifier("getAndAddField"))) + .single { it.owner.returnType.isLong() } + + val intPlusOperator = context.referenceFunctions(CallableId(StandardClassIds.Int, Name.identifier("plus"))) + .single { it.owner.valueParameters[0].type.isInt() } + + val longPlusOperator = context.referenceFunctions(CallableId(StandardClassIds.Long, Name.identifier("plus"))) + .single { it.owner.valueParameters[0].type.isLong() } + + // KMutableProperty0 + fun kMutableProperty0Type(typeArg: IrType): IrType = + buildSimpleType(irBuiltIns.kMutableProperty0Class, listOf(typeArg)) + + // () -> KMutableProperty0 + fun kMutableProperty0GetterType(typeArg: IrType): IrType = function0Type(kMutableProperty0Type(typeArg)) + + fun getParameterizedAtomicArrayType(elementType: IrType): IrType { + val atomicArrayClassSymbol = getAtomicArrayClassByValueType(elementType) + return if (atomicArrayClassSymbol == atomicRefArrayClassSymbol) { + buildSimpleType(atomicArrayClassSymbol, listOf(elementType)) + } else { + atomicArrayClassSymbol.defaultType + } + } + + override fun createBuilder(symbol: IrSymbol, startOffset: Int, endOffset: Int) = + NativeAtomicfuIrBuilder(this, symbol, startOffset, endOffset) +} diff --git a/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/native/NativeAtomicfuIrBuilder.kt b/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/native/NativeAtomicfuIrBuilder.kt new file mode 100644 index 00000000000..695cb12ff7f --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/backend/native/NativeAtomicfuIrBuilder.kt @@ -0,0 +1,395 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlinx.atomicfu.compiler.backend.native + +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter +import org.jetbrains.kotlin.ir.builders.declarations.buildFun +import org.jetbrains.kotlin.ir.declarations.IrProperty +import org.jetbrains.kotlin.ir.declarations.IrValueParameter +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.impl.* +import org.jetbrains.kotlin.ir.symbols.IrClassSymbol +import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol +import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlinx.atomicfu.compiler.backend.common.AbstractAtomicSymbols +import org.jetbrains.kotlinx.atomicfu.compiler.backend.common.AbstractAtomicfuIrBuilder + +class NativeAtomicfuIrBuilder( + override val atomicSymbols: NativeAtomicSymbols, + symbol: IrSymbol, + startOffset: Int, + endOffset: Int +): AbstractAtomicfuIrBuilder(atomicSymbols.irBuiltIns, symbol, startOffset, endOffset) { + + internal fun irCallAtomicNativeIntrinsic( + functionName: String, + propertyRef: IrExpression, + valueType: IrType, + returnType: IrType, + valueArguments: List + ): IrExpression = when (functionName) { + "" -> atomicGetField(propertyRef, valueType) + "", "lazySet" -> atomicSetField(propertyRef, valueType, valueArguments[0]) + "compareAndSet" -> compareAndSetField(propertyRef, valueType, valueArguments[0], valueArguments[1]) + "getAndSet" -> getAndSetField(propertyRef, valueType, valueArguments[0]) + "getAndAdd" -> getAndAddField(propertyRef, valueType, valueArguments[0]) + "getAndIncrement" -> getAndIncrementField(propertyRef, valueType) + "getAndDecrement" -> getAndDecrementField(propertyRef, valueType) + "addAndGet" -> addAndGetField(propertyRef, valueType, valueArguments[0]) + "incrementAndGet" -> incrementAndGetField(propertyRef, valueType) + "decrementAndGet" -> decrementAndGetField(propertyRef, valueType) + else -> error("Unsupported atomic function name $functionName") + }.let { + if (returnType.isBoolean()) irImplicitCast(it, atomicSymbols.irBuiltIns.booleanType) else it + } + + private fun atomicGetField(receiver: IrExpression?, valueType: IrType): IrCall = + irCall(atomicSymbols.atomicGetFieldIntrinsic).apply { + extensionReceiver = receiver + putTypeArgument(0, valueType) + } + + private fun atomicSetField(receiver: IrExpression?, valueType: IrType, newValue: IrExpression?): IrCall = + irCall(atomicSymbols.atomicSetFieldIntrinsic).apply { + extensionReceiver = receiver + putTypeArgument(0, valueType) + putValueArgument(0, newValue) + } + + private fun compareAndSetField(propertyRef: IrExpression, valueType: IrType, expected: IrExpression?, updated: IrExpression?) = + callNativeAtomicIntrinsic(propertyRef, atomicSymbols.compareAndSetFieldIntrinsic, valueType, expected, updated) + + private fun getAndSetField(propertyRef: IrExpression, valueType: IrType, value: IrExpression?) = + callNativeAtomicIntrinsic(propertyRef, atomicSymbols.getAndSetFieldIntrinsic, valueType, value) + + private fun getAndAddField(propertyRef: IrExpression, valueType: IrType, delta: IrExpression?): IrCall = + when { + valueType.isInt() -> + callNativeAtomicIntrinsic(propertyRef, atomicSymbols.getAndAddIntFieldIntrinsic, null, delta) + valueType.isLong() -> + callNativeAtomicIntrinsic( + propertyRef, + atomicSymbols.getAndAddLongFieldIntrinsic, + null, + delta?.implicitCastTo(context.irBuiltIns.longType) + ) + else -> error("kotlin.native.internal/getAndAddField intrinsic is not supported for values of type ${valueType.dumpKotlinLike()}") + } + + private fun addAndGetField(propertyRef: IrExpression, valueType: IrType, delta: IrExpression?): IrCall = + getAndAddField(propertyRef, valueType, delta).plus(delta) + + private fun getAndIncrementField(propertyRef: IrExpression, valueType: IrType): IrCall { + val delta = if (valueType.isInt()) irInt(1) else irLong(1) + return getAndAddField(propertyRef, valueType, delta) + } + + private fun getAndDecrementField(propertyRef: IrExpression, valueType: IrType): IrCall { + val delta = if (valueType.isInt()) irInt(-1) else irLong(-1) + return getAndAddField(propertyRef, valueType, delta) + } + + private fun incrementAndGetField(propertyRef: IrExpression, valueType: IrType): IrCall { + val delta = if (valueType.isInt()) irInt(1) else irLong(1) + return addAndGetField(propertyRef, valueType, delta) + } + + private fun decrementAndGetField(propertyRef: IrExpression, valueType: IrType): IrCall { + val delta = if (valueType.isInt()) irInt(-1) else irLong(-1) + return addAndGetField(propertyRef, valueType, delta) + } + + private fun callNativeAtomicIntrinsic( + propertyRef: IrExpression, + symbol: IrSimpleFunctionSymbol, + typeArgument: IrType?, + vararg valueArguments: IrExpression? + ): IrCall = + irCall(symbol).apply { + extensionReceiver = propertyRef + typeArgument?.let { putTypeArgument(0, it) } + valueArguments.forEachIndexed { index, arg -> + putValueArgument(index, arg) + } + } + + private fun invokePropertyGetter(refGetter: IrExpression) = irCall(atomicSymbols.invoke0Symbol).apply { dispatchReceiver = refGetter } + + /* + inline fun loop$atomicfu(refGetter: () -> KMutableProperty0, action: (T) -> Unit) { + while (true) { + val cur = refGetter().get() + action(cur) + } + } + */ + override fun atomicfuLoopBody(valueType: IrType, valueParameters: List): IrBlockBody = + irBlockBody { + val refGetter = valueParameters[0] + val action = valueParameters[1] + +irWhile().apply { + condition = irTrue() + body = irBlock { + val cur = createTmpVariable( + atomicGetField(invokePropertyGetter(irGet(refGetter)), valueType), + "atomicfu\$cur", false + ) + +irCall(atomicSymbols.invoke1Symbol).apply { + dispatchReceiver = irGet(action) + putValueArgument(0, irGet(cur)) + } + } + } + } + + /* + inline fun loop$atomicfu$array(atomicArray: AtomicArray, index: Int, action: (T) -> Unit) { + while (true) { + val cur = atomicArray.get(index) + action(cur) + } + } + */ + + override fun atomicfuArrayLoopBody(atomicArrayClass: IrClassSymbol, valueType: IrType, valueParameters: List): IrBlockBody = + irBlockBody { + val array = valueParameters[0] + val index = valueParameters[1] + val action = valueParameters[2] + +irWhile().apply { + condition = irTrue() + body = irBlock { + val atomicArrayClassSymbol = (array.type as IrSimpleType).classOrNull ?: error("Failed to obtain the class corresponding to the array type ${array.render()}") + val cur = createTmpVariable( + atomicGetArrayElement(atomicArrayClassSymbol, valueType, irGet(array), irGet(index)), + "atomicfu\$cur", false + ) + +irCall(atomicSymbols.invoke1Symbol).apply { + dispatchReceiver = irGet(action) + putValueArgument(0, irGet(cur)) + } + } + } + } + + /* + inline fun update$atomicfu(refGetter: () -> KMutableProperty0, action: (Int) -> Int) { + while (true) { + val cur = refGetter().get() + val upd = action(cur) + if (refGetter().compareAndSetField(cur, upd)) return + } + } + + + inline fun getAndUpdate$atomicfu(refGetter: () -> KMutableProperty0, action: (Int) -> Int): Int { + while (true) { + val cur = refGetter().get() + val upd = action(cur) + if (refGetter().compareAndSetField(cur, upd)) return cur + } + } + + inline fun getAndUpdate$atomicfu(refGetter: () -> KMutableProperty0, action: (Int) -> Int): Int { + while (true) { + val cur = refGetter().get() + val upd = action(cur) + if (refGetter().compareAndSetField(cur, upd)) return upd + } + } + */ + + override fun atomicfuUpdateBody(functionName: String, valueType: IrType, valueParameters: List): IrBlockBody = + irBlockBody { + val refGetter = valueParameters[0] + val action = valueParameters[1] + +irWhile().apply { + condition = irTrue() + body = irBlock { + val cur = createTmpVariable( + atomicGetField(invokePropertyGetter(irGet(refGetter)), valueType), + "atomicfu\$cur", false + ) + val upd = createTmpVariable( + irCall(atomicSymbols.invoke1Symbol).apply { + dispatchReceiver = irGet(action) + putValueArgument(0, irGet(cur)) + }, "atomicfu\$upd", false + ) + +irIfThen( + type = atomicSymbols.irBuiltIns.unitType, + condition = irCallAtomicNativeIntrinsic( + functionName = "compareAndSet", + propertyRef = invokePropertyGetter(irGet(refGetter)), + valueType = valueType, + returnType = atomicSymbols.irBuiltIns.booleanType, + valueArguments = listOf(irGet(cur), irGet(upd)) + ), + thenPart = when (functionName) { + "update" -> irReturnUnit() + "getAndUpdate" -> irReturn(irGet(cur)) + "updateAndGet" -> irReturn(irGet(upd)) + else -> error("Unsupported atomicfu inline loop function name: $functionName") + } + ) + } + } + } + + /* + inline fun update$atomicfu$array(atomicArray: AtomicArray, index: Int, action: (Int) -> Int) { + while (true) { + val cur = atomicArray[index] + val upd = action(cur) + if (atomicArray.compareAndSet(index, cur, upd)) return + } + } + + + inline fun getAndUpdate$atomicfu$array(atomicArray: AtomicArray, index: Int, action: (Int) -> Int): Int { + while (true) { + val cur = atomicArray[index] + val upd = action(cur) + if (atomicArray.compareAndSet(index, cur, upd)) return cur + } + } + + inline fun getAndUpdate$atomicfu$array(atomicArray: AtomicArray, index: Int, action: (Int) -> Int): Int { + while (true) { + val cur = atomicArray[index] + val upd = action(cur) + if (atomicArray.compareAndSet(index, cur, upd)) return upd + } + } + */ + + override fun atomicfuArrayUpdateBody( + functionName: String, + valueType: IrType, + atomicArrayClass: IrClassSymbol, + valueParameters: List + ): IrBlockBody = + irBlockBody { + val array = valueParameters[0] + val index = valueParameters[1] + val action = valueParameters[2] + +irWhile().apply { + condition = irTrue() + body = irBlock { + val atomicArrayClassSymbol = (array.type as IrSimpleType).classOrNull ?: error("Failed to obtain the class corresponding to the array type ${array.render()}") + val cur = createTmpVariable( + atomicGetArrayElement(atomicArrayClassSymbol, valueType, irGet(array), irGet(index)), + "atomicfu\$cur", false + ) + val upd = createTmpVariable( + irCall(atomicSymbols.invoke1Symbol).apply { + dispatchReceiver = irGet(action) + putValueArgument(0, irGet(cur)) + }, "atomicfu\$upd", false + ) + +irIfThen( + type = atomicSymbols.irBuiltIns.unitType, + condition = callAtomicArray( + arrayClassSymbol = atomicArrayClassSymbol, + functionName = "compareAndSet", + dispatchReceiver = irGet(array), + index = irGet(index), + valueArguments = listOf(irGet(cur), irGet(upd)), + isBooleanReceiver = valueType.isBoolean() + ), + thenPart = when (functionName) { + "update" -> irReturnUnit() + "getAndUpdate" -> irReturn(if (valueType.isBoolean() && irGet(cur).type.isInt()) irGet(cur).toBoolean() else irGet(cur)) + "updateAndGet" -> irReturn(if (valueType.isBoolean() && irGet(cur).type.isInt()) irGet(upd).toBoolean() else irGet(upd)) + else -> error("Unsupported atomicfu inline loop function name: $functionName") + } + ) + } + } + } + + private fun IrCall.plus(other: IrExpression?): IrCall { + val returnType = this.symbol.owner.returnType + val plusOperatorSymbol = when { + returnType.isInt() -> atomicSymbols.intPlusOperator + returnType.isLong() -> atomicSymbols.longPlusOperator + else -> error("Return type of the function ${this.symbol.owner.dump()} is expected to be Int or Long, but found $returnType") + } + return irCall(plusOperatorSymbol).apply { + dispatchReceiver = this@plus + putValueArgument(0, other) + } + } + + fun irPropertyReference(property: IrProperty, classReceiver: IrExpression?): IrPropertyReferenceImpl { + val backingField = requireNotNull(property.backingField) { "Backing field of the property $property should not be null" } + return IrPropertyReferenceImpl( + UNDEFINED_OFFSET, UNDEFINED_OFFSET, + type = atomicSymbols.buildSimpleType(context.irBuiltIns.kMutableProperty0Class, listOf(backingField.type)), + symbol = property.symbol, + typeArgumentsCount = 1, + field = backingField.symbol, + getter = property.getter?.symbol, + setter = property.setter?.symbol + ).apply { + putTypeArgument(0, backingField.type) + dispatchReceiver = classReceiver + } + } + + override fun newAtomicArray( + atomicArrayClass: IrClassSymbol, + size: IrExpression, + dispatchReceiver: IrExpression? + ): IrFunctionAccessExpression = + when (atomicArrayClass) { + atomicSymbols.atomicIntArrayClassSymbol, atomicSymbols.atomicLongArrayClassSymbol -> { + irCall(atomicSymbols.getAtomicArrayConstructor(atomicArrayClass)).apply { + putValueArgument(0, size) // size + this.dispatchReceiver = dispatchReceiver + } + } + atomicSymbols.atomicRefArrayClassSymbol -> { + val factoryFunction = atomicSymbols.getAtomicArrayConstructor(atomicArrayClass).owner + irCall(factoryFunction).apply { + val initType = atomicSymbols.function1Type(atomicSymbols.irBuiltIns.intType, atomicSymbols.irBuiltIns.anyNType) + val nullLambda = IrFunctionExpressionImpl( + SYNTHETIC_OFFSET, SYNTHETIC_OFFSET, + type = initType, + function = atomicSymbols.irBuiltIns.irFactory.buildFun { + name = Name.identifier("") + origin = AbstractAtomicSymbols.ATOMICFU_GENERATED_FUNCTION + returnType = atomicSymbols.irBuiltIns.anyNType + visibility = DescriptorVisibilities.LOCAL + }.apply { + val lambda = this + addValueParameter("it", atomicSymbols.irBuiltIns.intType) + body = IrBlockBodyImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, listOf( + IrReturnImpl( + UNDEFINED_OFFSET, UNDEFINED_OFFSET, + type = atomicSymbols.irBuiltIns.nothingType, + returnTargetSymbol = lambda.symbol, + value = IrConstImpl.constNull(SYNTHETIC_OFFSET, SYNTHETIC_OFFSET, atomicSymbols.irBuiltIns.nothingNType) + ) + )) + parent = factoryFunction + }, + origin = IrStatementOrigin.LAMBDA + ) + putValueArgument(0, size) // size + putValueArgument(1, nullLambda) + this.dispatchReceiver = dispatchReceiver + } + } + else -> error("Unsupported atomic array class found: ${atomicArrayClass.owner.render()}") + } +} diff --git a/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/extensions/AtomicfuLoweringExtension.kt b/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/extensions/AtomicfuLoweringExtension.kt index b12bd4bc0c2..725f55edc94 100644 --- a/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/extensions/AtomicfuLoweringExtension.kt +++ b/plugins/atomicfu/atomicfu-compiler/src/org/jetbrains/kotlinx/atomicfu/compiler/extensions/AtomicfuLoweringExtension.kt @@ -17,22 +17,32 @@ import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.ir.visitors.acceptVoid import org.jetbrains.kotlin.platform.isJs import org.jetbrains.kotlin.platform.jvm.isJvm +import org.jetbrains.kotlin.platform.konan.isNative import org.jetbrains.kotlinx.atomicfu.compiler.backend.jvm.JvmAtomicSymbols import org.jetbrains.kotlinx.atomicfu.compiler.backend.js.AtomicfuJsIrTransformer import org.jetbrains.kotlinx.atomicfu.compiler.backend.jvm.AtomicfuJvmIrTransformer +import org.jetbrains.kotlinx.atomicfu.compiler.backend.native.AtomicfuNativeIrTransformer +import org.jetbrains.kotlinx.atomicfu.compiler.backend.native.NativeAtomicSymbols public open class AtomicfuLoweringExtension : IrGenerationExtension { override fun generate( moduleFragment: IrModuleFragment, pluginContext: IrPluginContext ) { - if (pluginContext.platform.isJvm()) { - val atomicSymbols = JvmAtomicSymbols(pluginContext, moduleFragment) - AtomicfuJvmIrTransformer(pluginContext, atomicSymbols).transform(moduleFragment) - } - if (pluginContext.platform.isJs()) { - for (file in moduleFragment.files) { - AtomicfuClassLowering(pluginContext).runOnFileInOrder(file) + val platform = pluginContext.platform + when { + platform.isJvm() -> { + val atomicSymbols = JvmAtomicSymbols(pluginContext, moduleFragment) + AtomicfuJvmIrTransformer(pluginContext, atomicSymbols).transform(moduleFragment) + } + platform.isNative() -> { + val atomicSymbols = NativeAtomicSymbols(pluginContext, moduleFragment) + AtomicfuNativeIrTransformer(pluginContext, atomicSymbols).transform(moduleFragment) + } + platform.isJs() -> { + for (file in moduleFragment.files) { + AtomicfuClassLowering(pluginContext).runOnFileInOrder(file) + } } } } diff --git a/plugins/atomicfu/atomicfu-compiler/testData/box/atomic_extensions/ArrayInlineExtensionTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/atomic_extensions/ArrayInlineExtensionTest.kt index 098ea73f7a1..219b6ea58b7 100644 --- a/plugins/atomicfu/atomicfu-compiler/testData/box/atomic_extensions/ArrayInlineExtensionTest.kt +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/atomic_extensions/ArrayInlineExtensionTest.kt @@ -1,13 +1,9 @@ import kotlinx.atomicfu.* import kotlin.test.* -class ArrayInlineExtensionTest { +class AtomicIntArrayInlineExtensionTest { private val intArr = AtomicIntArray(10) - private val a = atomic(100) - private val longArr = AtomicLongArray(10) - private val refArr = atomicArrayOfNulls(5) - - class A(val s: String) + private val a = atomic(0) private inline fun casLoop(to: Int): Int { intArr[0].loop { cur -> @@ -16,8 +12,8 @@ class ArrayInlineExtensionTest { } } - private inline fun casLoopExpression(to: Long): Long = longArr[3].loop { cur -> - if (longArr[3].compareAndSet(cur, to)) return longArr[3].value + private inline fun casLoopExpression(to: Int): Int = intArr[3].loop { cur -> + if (intArr[3].compareAndSet(cur, to)) return intArr[3].value return 777 } @@ -60,7 +56,7 @@ class ArrayInlineExtensionTest { return foo(value + delta) } - fun testIntExtensionLoops() { + fun test() { assertEquals(5, casLoop(5)) assertEquals(6, casLoopExpression(6)) assertEquals(66, intArr[1].extensionLoop(66)) @@ -72,8 +68,161 @@ class ArrayInlineExtensionTest { } } +class AtomicLongArrayInlineExtensionTest { + private val longArr = AtomicLongArray(10) + private val a = atomic(0L) + + private inline fun casLoop(to: Long): Long { + longArr[0].loop { cur -> + if (longArr[0].compareAndSet(cur, to)) return longArr[0].value + return 777L + } + } + + private inline fun casLoopExpression(to: Long): Long = longArr[3].loop { cur -> + if (longArr[3].compareAndSet(cur, to)) return longArr[3].value + return 777L + } + + private inline fun AtomicLong.extensionLoop(to: Long): Long { + loop { cur -> + if (compareAndSet(cur, to)) return value + return 777L + } + } + + private inline fun AtomicLong.extensionLoopExpression(to: Long): Long = loop { cur -> + lazySet(cur + 10L) + return if (compareAndSet(cur, to)) value else incrementAndGet() + } + + private inline fun AtomicLong.extensionLoopMixedReceivers(first: Long, second: Long, index: Int): Long { + loop { cur -> + compareAndSet(cur, first) + longArr[index].compareAndSet(first, second) + return value + } + } + + private inline fun AtomicLong.extensionLoopRecursive(to: Long): Long { + loop { cur -> + compareAndSet(cur, to) + a.extensionLoop(5L) + return value + } + } + + private inline fun AtomicLong.foo(to: Long): Long { + loop { cur -> + if (compareAndSet(cur, to)) return 777L + else return value + } + } + + private inline fun AtomicLong.bar(delta: Long): Long { + return foo(value + delta) + } + + fun test() { + assertEquals(5L, casLoop(5L)) + assertEquals(6L, casLoopExpression(6L)) + assertEquals(66L, longArr[1].extensionLoop(66L)) + assertEquals(66L, longArr[2].extensionLoop(66L)) + assertEquals(77L, longArr[1].extensionLoopExpression(777L)) + assertEquals(99L, longArr[1].extensionLoopMixedReceivers(88L, 99L, 1)) + assertEquals(100L, longArr[1].extensionLoopRecursive(100L)) + assertEquals(777L, longArr[1].bar(100L)) + } +} + +class AtomicBooleanArrayInlineExtensionTest { + private val booleanArr = AtomicBooleanArray(10) + + private inline fun casLoop(to: Boolean): Boolean { + booleanArr[0].loop { cur -> + if (booleanArr[0].compareAndSet(cur, to)) return booleanArr[0].value + } + } + + private inline fun casLoopExpression(to: Boolean): Boolean = booleanArr[3].loop { cur -> + if (booleanArr[3].compareAndSet(cur, to)) return booleanArr[3].value + } + + private inline fun AtomicBoolean.extensionLoop(to: Boolean): Boolean { + loop { cur -> + if (compareAndSet(cur, to)) return value + } + } + + private inline fun AtomicBoolean.extensionLoopExpression(to: Boolean): Boolean = loop { cur -> + lazySet(false) + return if (compareAndSet(cur, to)) value else !value + } + + private inline fun AtomicBoolean.extensionLoopMixedReceivers(first: Boolean, second: Boolean, index: Int): Boolean { + loop { cur -> + compareAndSet(cur, first) + booleanArr[index].compareAndSet(first, second) + return value + } + } + + fun test() { + assertEquals(true, casLoop(true)) + assertEquals(true, casLoopExpression(true)) + assertEquals(true, booleanArr[1].extensionLoop(true)) + assertEquals(true, booleanArr[1].extensionLoopExpression(true)) + assertEquals(false, booleanArr[7].extensionLoopMixedReceivers(true, false, 7)) + } +} + +class AtomicRefArrayInlineExtensionTest { + private val refArr = atomicArrayOfNulls(10) + private val a = atomic(0L) + + private inline fun casLoop(to: String): String? { + refArr[0].loop { cur -> + if (refArr[0].compareAndSet(cur, to)) return refArr[0].value + } + } + + private inline fun casLoopExpression(to: String): String? = refArr[3].loop { cur -> + if (refArr[3].compareAndSet(cur, to)) return refArr[3].value + } + + private inline fun AtomicRef.extensionLoop(to: String): String? { + loop { cur -> + if (compareAndSet(cur, to)) return value + else "incorrect" + } + } + + private inline fun AtomicRef.extensionLoopExpression(to: String): String? = loop { cur -> + lazySet("aaa") + return if (compareAndSet(cur, to)) value else "CAS_failed" + } + + private inline fun AtomicRef.extensionLoopMixedReceivers(first: String, second: String, index: Int): String? { + loop { cur -> + compareAndSet(cur, first) + refArr[index].compareAndSet(first, second) + return value + } + } + + fun test() { + assertEquals("aaa", casLoop("aaa")) + assertEquals("bbb", casLoopExpression("bbb")) + assertEquals("ccc", refArr[1].extensionLoop("ccc")) + assertEquals("CAS_failed", refArr[1].extensionLoopExpression("ccc")) + assertEquals("bbb", refArr[7].extensionLoopMixedReceivers("aaa", "bbb", 7)) + } +} + fun box(): String { - val testClass = ArrayInlineExtensionTest() - testClass.testIntExtensionLoops() + AtomicIntArrayInlineExtensionTest().test() + AtomicLongArrayInlineExtensionTest().test() + AtomicBooleanArrayInlineExtensionTest().test() + AtomicRefArrayInlineExtensionTest().test() return "OK" } diff --git a/plugins/atomicfu/atomicfu-compiler/testData/box/atomic_extensions/ArrayInlineExtensionTest.txt b/plugins/atomicfu/atomicfu-compiler/testData/box/atomic_extensions/ArrayInlineExtensionTest.txt index 3a8e2492985..9f4e21b9299 100644 --- a/plugins/atomicfu/atomicfu-compiler/testData/box/atomic_extensions/ArrayInlineExtensionTest.txt +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/atomic_extensions/ArrayInlineExtensionTest.txt @@ -1,26 +1,40 @@ @kotlin.Metadata -public final class ArrayInlineExtensionTest$A { +public final class ArrayInlineExtensionTestKt { // source: 'ArrayInlineExtensionTest.kt' - private final @org.jetbrains.annotations.NotNull field s: java.lang.String - public method (@org.jetbrains.annotations.NotNull p0: java.lang.String): void - public final @org.jetbrains.annotations.NotNull method getS(): java.lang.String - public final inner class ArrayInlineExtensionTest$A + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String } @kotlin.Metadata -public final class ArrayInlineExtensionTest { +public final class AtomicBooleanArrayInlineExtensionTest { + // source: 'ArrayInlineExtensionTest.kt' + private synthetic final field booleanArr: java.util.concurrent.atomic.AtomicIntegerArray + public method (): void + private final method casLoop(p0: boolean): boolean + private final method casLoopExpression(p0: boolean): boolean + private synthetic final method extensionLoop$atomicfu$array(p0: java.util.concurrent.atomic.AtomicIntegerArray, p1: int, p2: boolean): boolean + private synthetic final method extensionLoop$atomicfu(p0: java.lang.Object, p1: java.util.concurrent.atomic.AtomicIntegerFieldUpdater, p2: boolean): boolean + private synthetic final method extensionLoopExpression$atomicfu$array(p0: java.util.concurrent.atomic.AtomicIntegerArray, p1: int, p2: boolean): boolean + private synthetic final method extensionLoopExpression$atomicfu(p0: java.lang.Object, p1: java.util.concurrent.atomic.AtomicIntegerFieldUpdater, p2: boolean): boolean + private synthetic final method extensionLoopMixedReceivers$atomicfu$array(p0: java.util.concurrent.atomic.AtomicIntegerArray, p1: int, p2: boolean, p3: boolean, p4: int): boolean + private synthetic final method extensionLoopMixedReceivers$atomicfu(p0: java.lang.Object, p1: java.util.concurrent.atomic.AtomicIntegerFieldUpdater, p2: boolean, p3: boolean, p4: int): boolean + private synthetic final method getBooleanArr(): java.util.concurrent.atomic.AtomicIntegerArray + private synthetic final method loop$atomicfu$array(p0: java.util.concurrent.atomic.AtomicIntegerArray, p1: int, p2: kotlin.jvm.functions.Function1): void + private synthetic final method loop$atomicfu(p0: java.lang.Object, p1: java.util.concurrent.atomic.AtomicIntegerFieldUpdater, p2: kotlin.jvm.functions.Function1): void + public final method test(): void +} + +@kotlin.Metadata +public final class AtomicIntArrayInlineExtensionTest { // source: 'ArrayInlineExtensionTest.kt' private synthetic final static field a$volatile$FU: java.util.concurrent.atomic.AtomicIntegerFieldUpdater private synthetic volatile field a$volatile: int private synthetic final field intArr: java.util.concurrent.atomic.AtomicIntegerArray - private synthetic final field longArr: java.util.concurrent.atomic.AtomicLongArray - private synthetic final field refArr: java.util.concurrent.atomic.AtomicReferenceArray static method (): void public method (): void private synthetic final method bar$atomicfu$array(p0: java.util.concurrent.atomic.AtomicIntegerArray, p1: int, p2: int): int private synthetic final method bar$atomicfu(p0: java.lang.Object, p1: java.util.concurrent.atomic.AtomicIntegerFieldUpdater, p2: int): int private final method casLoop(p0: int): int - private final method casLoopExpression(p0: long): long + private final method casLoopExpression(p0: int): int private synthetic final method extensionLoop$atomicfu$array(p0: java.util.concurrent.atomic.AtomicIntegerArray, p1: int, p2: int): int private synthetic final method extensionLoop$atomicfu(p0: java.lang.Object, p1: java.util.concurrent.atomic.AtomicIntegerFieldUpdater, p2: int): int private synthetic final method extensionLoopExpression$atomicfu$array(p0: java.util.concurrent.atomic.AtomicIntegerArray, p1: int, p2: int): int @@ -34,18 +48,64 @@ public final class ArrayInlineExtensionTest { private synthetic final static method getA$volatile$FU(): java.util.concurrent.atomic.AtomicIntegerFieldUpdater private synthetic final method getA$volatile(): int private synthetic final method getIntArr(): java.util.concurrent.atomic.AtomicIntegerArray - private synthetic final method getLongArr(): java.util.concurrent.atomic.AtomicLongArray - private synthetic final method getRefArr(): java.util.concurrent.atomic.AtomicReferenceArray private synthetic final method loop$atomicfu$array(p0: java.util.concurrent.atomic.AtomicIntegerArray, p1: int, p2: kotlin.jvm.functions.Function1): void - private synthetic final method loop$atomicfu$array(p0: java.util.concurrent.atomic.AtomicLongArray, p1: int, p2: kotlin.jvm.functions.Function1): void private synthetic final method loop$atomicfu(p0: java.lang.Object, p1: java.util.concurrent.atomic.AtomicIntegerFieldUpdater, p2: kotlin.jvm.functions.Function1): void private synthetic final method setA$volatile(p0: int): void - public final method testIntExtensionLoops(): void - public final inner class ArrayInlineExtensionTest$A + public final method test(): void } @kotlin.Metadata -public final class ArrayInlineExtensionTestKt { +public final class AtomicLongArrayInlineExtensionTest { // source: 'ArrayInlineExtensionTest.kt' - public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String + private synthetic final static field a$volatile$FU: java.util.concurrent.atomic.AtomicLongFieldUpdater + private synthetic volatile field a$volatile: long + private synthetic final field longArr: java.util.concurrent.atomic.AtomicLongArray + static method (): void + public method (): void + private synthetic final method bar$atomicfu$array(p0: java.util.concurrent.atomic.AtomicLongArray, p1: int, p2: long): long + private synthetic final method bar$atomicfu(p0: java.lang.Object, p1: java.util.concurrent.atomic.AtomicLongFieldUpdater, p2: long): long + private final method casLoop(p0: long): long + private final method casLoopExpression(p0: long): long + private synthetic final method extensionLoop$atomicfu$array(p0: java.util.concurrent.atomic.AtomicLongArray, p1: int, p2: long): long + private synthetic final method extensionLoop$atomicfu(p0: java.lang.Object, p1: java.util.concurrent.atomic.AtomicLongFieldUpdater, p2: long): long + private synthetic final method extensionLoopExpression$atomicfu$array(p0: java.util.concurrent.atomic.AtomicLongArray, p1: int, p2: long): long + private synthetic final method extensionLoopExpression$atomicfu(p0: java.lang.Object, p1: java.util.concurrent.atomic.AtomicLongFieldUpdater, p2: long): long + private synthetic final method extensionLoopMixedReceivers$atomicfu$array(p0: java.util.concurrent.atomic.AtomicLongArray, p1: int, p2: long, p3: long, p4: int): long + private synthetic final method extensionLoopMixedReceivers$atomicfu(p0: java.lang.Object, p1: java.util.concurrent.atomic.AtomicLongFieldUpdater, p2: long, p3: long, p4: int): long + private synthetic final method extensionLoopRecursive$atomicfu$array(p0: java.util.concurrent.atomic.AtomicLongArray, p1: int, p2: long): long + private synthetic final method extensionLoopRecursive$atomicfu(p0: java.lang.Object, p1: java.util.concurrent.atomic.AtomicLongFieldUpdater, p2: long): long + private synthetic final method foo$atomicfu$array(p0: java.util.concurrent.atomic.AtomicLongArray, p1: int, p2: long): long + private synthetic final method foo$atomicfu(p0: java.lang.Object, p1: java.util.concurrent.atomic.AtomicLongFieldUpdater, p2: long): long + private synthetic final static method getA$volatile$FU(): java.util.concurrent.atomic.AtomicLongFieldUpdater + private synthetic final method getA$volatile(): long + private synthetic final method getLongArr(): java.util.concurrent.atomic.AtomicLongArray + private synthetic final method loop$atomicfu$array(p0: java.util.concurrent.atomic.AtomicLongArray, p1: int, p2: kotlin.jvm.functions.Function1): void + private synthetic final method loop$atomicfu(p0: java.lang.Object, p1: java.util.concurrent.atomic.AtomicLongFieldUpdater, p2: kotlin.jvm.functions.Function1): void + private synthetic final method setA$volatile(p0: long): void + public final method test(): void +} + +@kotlin.Metadata +public final class AtomicRefArrayInlineExtensionTest { + // source: 'ArrayInlineExtensionTest.kt' + private synthetic final static field a$volatile$FU: java.util.concurrent.atomic.AtomicLongFieldUpdater + private synthetic volatile field a$volatile: long + private synthetic final field refArr: java.util.concurrent.atomic.AtomicReferenceArray + static method (): void + public method (): void + private final method casLoop(p0: java.lang.String): java.lang.String + private final method casLoopExpression(p0: java.lang.String): java.lang.String + private synthetic final method extensionLoop$atomicfu$array(p0: java.util.concurrent.atomic.AtomicReferenceArray, p1: int, p2: java.lang.String): java.lang.String + private synthetic final method extensionLoop$atomicfu(p0: java.lang.Object, p1: java.util.concurrent.atomic.AtomicReferenceFieldUpdater, p2: java.lang.String): java.lang.String + private synthetic final method extensionLoopExpression$atomicfu$array(p0: java.util.concurrent.atomic.AtomicReferenceArray, p1: int, p2: java.lang.String): java.lang.String + private synthetic final method extensionLoopExpression$atomicfu(p0: java.lang.Object, p1: java.util.concurrent.atomic.AtomicReferenceFieldUpdater, p2: java.lang.String): java.lang.String + private synthetic final method extensionLoopMixedReceivers$atomicfu$array(p0: java.util.concurrent.atomic.AtomicReferenceArray, p1: int, p2: java.lang.String, p3: java.lang.String, p4: int): java.lang.String + private synthetic final method extensionLoopMixedReceivers$atomicfu(p0: java.lang.Object, p1: java.util.concurrent.atomic.AtomicReferenceFieldUpdater, p2: java.lang.String, p3: java.lang.String, p4: int): java.lang.String + private synthetic final static method getA$volatile$FU(): java.util.concurrent.atomic.AtomicLongFieldUpdater + private synthetic final method getA$volatile(): long + private synthetic final method getRefArr(): java.util.concurrent.atomic.AtomicReferenceArray + private synthetic final method loop$atomicfu$array(p0: java.util.concurrent.atomic.AtomicReferenceArray, p1: int, p2: kotlin.jvm.functions.Function1): void + private synthetic final method loop$atomicfu(p0: java.lang.Object, p1: java.util.concurrent.atomic.AtomicReferenceFieldUpdater, p2: kotlin.jvm.functions.Function1): void + private synthetic final method setA$volatile(p0: long): void + public final method test(): void }