Atomicfu compiler plugin for JS/IR backend
diff --git a/generators/build.gradle.kts b/generators/build.gradle.kts index 496b815..fad5447 100644 --- a/generators/build.gradle.kts +++ b/generators/build.gradle.kts
@@ -71,6 +71,7 @@ testCompile(projectTests(":kotlin-sam-with-receiver-compiler-plugin")) testCompile(projectTests(":kotlinx-serialization-compiler-plugin")) testCompile(projectTests(":plugins:fir:fir-plugin-prototype")) + testCompile(projectTests(":kotlinx-atomicfu-compiler-plugin")) testCompile(projectTests(":idea:jvm-debugger:jvm-debugger-test")) testCompile(projectTests(":generators:test-generator")) testCompile(projectTests(":idea"))
diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 2f93f03..1a40c95 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt
@@ -187,6 +187,9 @@ import org.jetbrains.kotlin.shortenRefs.AbstractFirShortenRefsTest import org.jetbrains.kotlin.shortenRefs.AbstractShortenRefsTest import org.jetbrains.kotlin.test.TargetBackend +import org.jetbrains.kotlin.tools.projectWizard.cli.AbstractBuildFileGenerationTest +import org.jetbrains.kotlinx.atomicfu.AbstractBasicAtomicfuTest +import org.jetbrains.kotlinx.atomicfu.AbstractLocksAtomicfuTest import org.jetbrains.kotlin.tools.projectWizard.cli.AbstractProjectTemplateBuildFileGenerationTest import org.jetbrains.kotlin.tools.projectWizard.cli.AbstractYamlBuildFileGenerationTest import org.jetbrains.kotlin.tools.projectWizard.wizard.AbstractProjectTemplateNewWizardProjectImportTest @@ -1787,6 +1790,18 @@ } } + testGroup( + "plugins/atomicfu/atomicfu-compiler/test", + "plugins/atomicfu/atomicfu-compiler/testData" + ) { + testClass<AbstractBasicAtomicfuTest> { + model("basic") + } + testClass<AbstractLocksAtomicfuTest> { + model("locks") + } + } + testGroup("plugins/fir/fir-plugin-prototype/tests", "plugins/fir/fir-plugin-prototype/testData") { testClass<AbstractFirAllOpenDiagnosticTest> { model("")
diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt index 4465da5..887aa15 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt
@@ -854,7 +854,7 @@ val libraries = when (targetBackend) { TargetBackend.JS_IR_ES6 -> dependencies - TargetBackend.JS_IR -> dependencies + TargetBackend.JS_IR -> dependencies + configuration[JSConfigurationKeys.LIBRARIES]!! TargetBackend.JS -> JsConfig.JS_STDLIB + JsConfig.JS_KOTLIN_TEST + dependencies else -> error("Unsupported target backend: $targetBackend") }
diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt index 3d72e26..cb35e88 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt
@@ -99,11 +99,13 @@ val transitiveLibraries = config.configuration[JSConfigurationKeys.TRANSITIVE_LIBRARIES]!!.map { File(it).name } + val libraries = config.configuration[JSConfigurationKeys.LIBRARIES] ?: emptyList() + val allKlibPaths = (runtimeKlibs + transitiveLibraries.map { compilationCache[it] ?: error("Can't find compiled module for dependency $it") }).map { File(it).absolutePath } - val resolvedLibraries = jsResolveLibraries(allKlibPaths, emptyList(), messageCollectorLogger(MessageCollector.NONE)) + val resolvedLibraries = jsResolveLibraries(allKlibPaths + libraries, emptyList(), messageCollectorLogger(MessageCollector.NONE)) val actualOutputFile = outputFile.absolutePath.let { if (!isMainModule) it.replace("_v5.js", "/") else it
diff --git a/libraries/configureGradleTools.gradle b/libraries/configureGradleTools.gradle index 35020d1..9015789 100644 --- a/libraries/configureGradleTools.gradle +++ b/libraries/configureGradleTools.gradle
@@ -1,5 +1,5 @@ -configure([project(':kotlin-gradle-plugin'), project(':kotlin-allopen'), project(':kotlin-noarg'), project(':kotlin-serialization')]) { project -> +configure([project(':kotlin-gradle-plugin'), project(':kotlin-allopen'), project(':kotlin-noarg'), project(':kotlin-serialization'), project(':atomicfu')]) { project -> apply plugin: 'com.gradle.plugin-publish' afterEvaluate {
diff --git a/libraries/tools/atomicfu/build.gradle b/libraries/tools/atomicfu/build.gradle new file mode 100644 index 0000000..740c4be --- /dev/null +++ b/libraries/tools/atomicfu/build.gradle
@@ -0,0 +1,39 @@ +repositories { + mavenCentral() +} + +apply plugin: 'kotlin' +apply plugin: 'jps-compatible' + +configurePublishing(project) + +pill { + variant = 'FULL' +} + +dependencies { + compileOnly project(':kotlin-gradle-plugin') + compileOnly project(':kotlin-gradle-plugin-api') + + compileOnly kotlinStdlib() + compileOnly project(path: ':kotlin-compiler-embeddable', configuration: 'runtimeJar') + + embedded(project(":kotlinx-atomicfu-compiler-plugin")) { transitive = false } +} + +jar { + manifestAttributes(manifest, project) +} + +ArtifactsKt.runtimeJar(project, EmbeddableKt.rewriteDefaultJarDepsToShadedCompiler(project, {}), {}) +configureSourcesJar() +configureJavadocJar() + +pluginBundle { + plugins { + atomicfu { + id = 'org.jetbrains.kotlin.plugin.atomicfu' + description = displayName = 'Kotlin compiler plugin for kotlinx.atomicfu library' + } + } +} \ No newline at end of file
diff --git a/libraries/tools/atomicfu/src/main/kotlin/org/jetbrains/kotlinx/atomicfu/gradle/AtomicfuSubplugin.kt b/libraries/tools/atomicfu/src/main/kotlin/org/jetbrains/kotlinx/atomicfu/gradle/AtomicfuSubplugin.kt new file mode 100644 index 0000000..eac1670 --- /dev/null +++ b/libraries/tools/atomicfu/src/main/kotlin/org/jetbrains/kotlinx/atomicfu/gradle/AtomicfuSubplugin.kt
@@ -0,0 +1,83 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlinx.atomicfu.gradle + +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.ExtensionAware +import org.gradle.api.tasks.compile.AbstractCompile +import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget +import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget +import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile +import org.jetbrains.kotlin.gradle.dsl.* + +class AtomicfuGradleSubplugin : Plugin<Project> { + companion object { + fun isEnabled(project: Project) = project.plugins.findPlugin(AtomicfuGradleSubplugin::class.java) != null + } + + override fun apply(project: Project) { + // nothing here + } +} + +class AtomicfuKotlinGradleSubplugin : KotlinGradleSubplugin<AbstractCompile> { + companion object { + const val ATOMICFU_ARTIFACT_NAME = "atomicfu" + } + + override fun isApplicable(project: Project, task: AbstractCompile) = + task is Kotlin2JsCompile && project.hasIrTargets() + + + private fun Project.hasIrTargets(): Boolean { + extensions.findByType(KotlinProjectExtension::class.java)?.let { kotlinExtension -> + if (kotlinExtension is KotlinJsProjectExtension) { + if (kotlinExtension._target?.isJsIrTarget() == true) return true + } + val targetsExtension = (kotlinExtension as? ExtensionAware)?.extensions?.findByName("targets") + @Suppress("UNCHECKED_CAST") + if (targetsExtension != null) { + val targets = targetsExtension as NamedDomainObjectContainer<KotlinTarget> + if (targets.any { it.isJsIrTarget() }) return true + } + } + return false + } + + private fun KotlinTarget.isJsIrTarget(): Boolean = + (this is KotlinJsTarget && this.irTarget != null) || this is KotlinJsIrTarget + + override fun apply( + project: Project, + kotlinCompile: AbstractCompile, + javaCompile: AbstractCompile?, + variantData: Any?, + androidProjectHandler: Any?, + kotlinCompilation: KotlinCompilation<*>? + ): List<SubpluginOption> { + return emptyList() + } + + override fun getPluginArtifact(): SubpluginArtifact = + JetBrainsSubpluginArtifact(ATOMICFU_ARTIFACT_NAME) + + override fun getCompilerPluginId() = "org.jetbrains.kotlinx.atomicfu" +}
diff --git a/libraries/tools/atomicfu/src/main/resources/META-INF/gradle-plugins/atomicfu-jsir.properties b/libraries/tools/atomicfu/src/main/resources/META-INF/gradle-plugins/atomicfu-jsir.properties new file mode 100644 index 0000000..899d3fd --- /dev/null +++ b/libraries/tools/atomicfu/src/main/resources/META-INF/gradle-plugins/atomicfu-jsir.properties
@@ -0,0 +1 @@ +implementation-class=org.jetbrains.kotlinx.atomicfu.gradle.AtomicfuGradleSubplugin \ No newline at end of file
diff --git a/libraries/tools/atomicfu/src/main/resources/META-INF/gradle-plugins/org.jetbrains.kotlin.plugin.atomicfu.properties b/libraries/tools/atomicfu/src/main/resources/META-INF/gradle-plugins/org.jetbrains.kotlin.plugin.atomicfu.properties new file mode 100644 index 0000000..899d3fd --- /dev/null +++ b/libraries/tools/atomicfu/src/main/resources/META-INF/gradle-plugins/org.jetbrains.kotlin.plugin.atomicfu.properties
@@ -0,0 +1 @@ +implementation-class=org.jetbrains.kotlinx.atomicfu.gradle.AtomicfuGradleSubplugin \ No newline at end of file
diff --git a/libraries/tools/atomicfu/src/main/resources/META-INF/services/org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin b/libraries/tools/atomicfu/src/main/resources/META-INF/services/org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin new file mode 100644 index 0000000..82e8567 --- /dev/null +++ b/libraries/tools/atomicfu/src/main/resources/META-INF/services/org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin
@@ -0,0 +1,17 @@ +# +# Copyright 2010-2020 JetBrains s.r.o. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +org.jetbrains.kotlinx.atomicfu.gradle.AtomicfuKotlinGradleSubplugin \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/build.gradle.kts b/plugins/atomicfu/atomicfu-compiler/build.gradle.kts new file mode 100644 index 0000000..37b1568 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/build.gradle.kts
@@ -0,0 +1,157 @@ +import org.jetbrains.kotlin.gradle.targets.js.KotlinJsCompilerAttribute +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages +import org.gradle.internal.os.OperatingSystem + + +description = "Atomicfu Compiler Plugin" + +plugins { + kotlin("jvm") + id("jps-compatible") + id("com.github.node-gradle.node") version "2.2.0" + id("de.undercouch.download") + id("com.gradle.enterprise.test-distribution") +} + +node { + download = true + version = "10.16.2" +} + +val antLauncherJar by configurations.creating +val testJsRuntime by configurations.creating { + attributes { + attribute(Usage.USAGE_ATTRIBUTE, objects.named(KotlinUsages.KOTLIN_RUNTIME)) + attribute(KotlinPlatformType.attribute, KotlinPlatformType.js) + } +} + +val atomicfuClasspath by configurations.creating { + attributes { + attribute(KotlinPlatformType.attribute, KotlinPlatformType.js) + attribute(KotlinJsCompilerAttribute.jsCompilerAttribute, KotlinJsCompilerAttribute.ir) + } +} + +val atomicfuRuntimeForTests by configurations.creating { + attributes { + attribute(KotlinPlatformType.attribute, KotlinPlatformType.js) + attribute(KotlinJsCompilerAttribute.jsCompilerAttribute, KotlinJsCompilerAttribute.ir) + attribute(Usage.USAGE_ATTRIBUTE, objects.named(KotlinUsages.KOTLIN_RUNTIME)) + } +} + +repositories { + mavenLocal() + jcenter() +} + +dependencies { + compileOnly(intellijCoreDep()) { includeJars("intellij-core", "asm-all", rootProject = rootProject) } + + compileOnly(project(":compiler:plugin-api")) + compileOnly(project(":compiler:cli-common")) + compileOnly(project(":compiler:frontend")) + compileOnly(project(":compiler:backend")) + compileOnly(project(":compiler:ir.backend.common")) + compileOnly(project(":js:js.frontend")) + compileOnly(project(":js:js.translator")) + compile(project(":compiler:backend.js")) + + runtimeOnly(kotlinStdlib()) + + testCompile(projectTests(":compiler:tests-common")) + testCompile(projectTests(":js:js.tests")) + testCompile(commonDep("junit:junit")) + + testRuntime(kotlinStdlib()) + testRuntime(project(":kotlin-reflect")) + testRuntime(project(":kotlin-preloader")) // it's required for ant tests + testRuntime(project(":compiler:backend-common")) + testRuntime(commonDep("org.fusesource.jansi", "jansi")) + + atomicfuClasspath("org.jetbrains.kotlinx:atomicfu-js:0.15.1") { + isTransitive = false + } + + atomicfuRuntimeForTests(project(":kotlinx-atomicfu-runtime")) { isTransitive = false } + + testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.6.2") +} + +sourceSets { + "main" { projectDefault() } + "test" { projectDefault() } +} + +runtimeJar() +sourcesJar() +javadocJar() +testsJar() + +projectTest(parallel = true) { + workingDir = rootDir + dependsOn(atomicfuRuntimeForTests) + doFirst { + systemProperty("atomicfuRuntimeForTests.classpath", atomicfuRuntimeForTests.asPath) + } + setUpJsBoxTests(jsEnabled = true, jsIrEnabled = true) +} + +enum class OsName { WINDOWS, MAC, LINUX, UNKNOWN } +enum class OsArch { X86_32, X86_64, UNKNOWN } +data class OsType(val name: OsName, val arch: OsArch) + +fun Test.setupV8() { + dependsOn(":js:js.tests:unzipV8") + val currentOsType = run { + val gradleOs = OperatingSystem.current() + val osName = when { + gradleOs.isMacOsX -> OsName.MAC + gradleOs.isWindows -> OsName.WINDOWS + gradleOs.isLinux -> OsName.LINUX + else -> OsName.UNKNOWN + } + + val osArch = when (System.getProperty("sun.arch.data.model")) { + "32" -> OsArch.X86_32 + "64" -> OsArch.X86_64 + else -> OsArch.UNKNOWN + } + + OsType(osName, osArch) + } + val v8osString = when (currentOsType) { + OsType(OsName.LINUX, OsArch.X86_32) -> "linux32" + OsType(OsName.LINUX, OsArch.X86_64) -> "linux64" + OsType(OsName.MAC, OsArch.X86_64) -> "mac64" + OsType(OsName.WINDOWS, OsArch.X86_32) -> "win32" + OsType(OsName.WINDOWS, OsArch.X86_64) -> "win64" + else -> error("unsupported os type $currentOsType") + } + val v8Path = "${rootDir.absolutePath}/js/js.tests/build/tools/v8-${v8osString}-rel-8.8.104/" + val v8ExecutablePath = File(v8Path, "d8") + systemProperty("javascript.engine.path.V8", v8ExecutablePath) + inputs.dir(v8Path) +} + +fun Test.setUpJsBoxTests(jsEnabled: Boolean, jsIrEnabled: Boolean) { + setupV8() + + dependsOn(":dist") + if (jsIrEnabled) { + dependsOn(":kotlin-stdlib-js-ir:compileKotlinJs") + systemProperty("kotlin.js.full.stdlib.path", "libraries/stdlib/js-ir/build/classes/kotlin/js/main") + dependsOn(":kotlin-stdlib-js-ir-minimal-for-test:compileKotlinJs") + systemProperty("kotlin.js.reduced.stdlib.path", "libraries/stdlib/js-ir-minimal-for-test/build/classes/kotlin/js/main") + dependsOn(":kotlin-test:kotlin-test-js-ir:compileKotlinJs") + systemProperty("kotlin.js.kotlin.test.path", "libraries/kotlin.test/js-ir/build/classes/kotlin/js/main") + systemProperty("kotlin.js.kotlin.test.path", "libraries/kotlin.test/js-ir/build/classes/kotlin/js/main") + systemProperty("kotlin.js.test.root.out.dir", "$buildDir/") + systemProperty("atomicfu.classpath", atomicfuClasspath.asPath) + } +} + +val generateTests by generator("org.jetbrains.kotlin.generators.tests.GenerateJsTestsKt") +val testDataDir = project(":js:js.translator").projectDir.resolve("testData") \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar b/plugins/atomicfu/atomicfu-compiler/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar new file mode 100644 index 0000000..b3e8636 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
@@ -0,0 +1,17 @@ +# +# Copyright 2010-2017 JetBrains s.r.o. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +org.jetbrains.kotlinx.atomicfu.compiler.extensions.AtomicfuComponentRegistrar \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/AtomicFUTransformerJsIr.kt b/plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/AtomicFUTransformerJsIr.kt new file mode 100644 index 0000000..9e715f0 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/AtomicFUTransformerJsIr.kt
@@ -0,0 +1,596 @@ +/* + * Copyright 2010-2020 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.extensions + +import org.jetbrains.kotlin.backend.common.deepCopyWithVariables +import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.ir.* +import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder.buildValueParameter +import org.jetbrains.kotlin.ir.builders.declarations.buildTypeParameter +import org.jetbrains.kotlin.ir.expressions.impl.* +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.symbols.* +import org.jetbrains.kotlin.ir.symbols.impl.IrClassSymbolImpl +import org.jetbrains.kotlin.ir.symbols.impl.IrTypeParameterSymbolImpl +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.name.* + +private const val KOTLIN = "kotlin" +private const val AFU_PKG = "kotlinx/atomicfu" +private const val LOCKS = "locks" +private const val ATOMIC_CONSTRUCTOR = "atomic" +private const val ATOMICFU_VALUE_TYPE = """Atomic(Int|Long|Boolean|Ref)""" +private const val ATOMIC_ARRAY_TYPE = """Atomic(Int|Long|Boolean|)Array""" +private const val ATOMIC_ARRAY_FACTORY_FUNCTION = "atomicArrayOfNulls" +private const val ATOMICFU_RUNTIME_FUNCTION_PREDICATE = "atomicfu_" +private const val REENTRANT_LOCK_TYPE = "ReentrantLock" +private const val GETTER = "atomicfu\$getter" +private const val SETTER = "atomicfu\$setter" +private const val GET = "get" +private const val SET = "set" +private const val ATOMICFU_INLINE_FUNCTION = """atomicfu_(loop|update|getAndUpdate|updateAndGet)""" + +private fun String.prettyStr() = replace('/', '.') + +class AtomicFUTransformer(override val context: IrPluginContext) : IrElementTransformerVoid(), TransformerHelpers { + + private val irBuiltIns = context.irBuiltIns + + private val AFU_CLASSES: Map<String, IrType> = mapOf( + "AtomicInt" to irBuiltIns.intType, + "AtomicLong" to irBuiltIns.longType, + "AtomicRef" to irBuiltIns.anyType, + "AtomicBoolean" to irBuiltIns.booleanType + ) + + private val AFU_ARRAY_CLASSES: Map<String, String> = mapOf( + "AtomicIntArray" to "IntArray", + "AtomicLongArray" to "LongArray", + "AtomicBooleanArray" to "BooleanArray", + "AtomicArray" to "Array" + ) + + override fun visitFile(declaration: IrFile): IrFile { + val newDeclarations = mutableListOf<IrDeclaration>() + declaration.declarations.forEachIndexed { index, irDeclaration -> + irDeclaration.transformAtomicInlineDeclaration()?.let { newDeclarations.add(it) } + } + declaration.declarations.addAll(newDeclarations) + return super.visitFile(declaration) + } + + override fun visitClass(declaration: IrClass): IrStatement { + val newDeclarations = mutableListOf<IrDeclaration>() + declaration.declarations.forEachIndexed { index, irDeclaration -> + irDeclaration.transformAtomicInlineDeclaration()?.let { newDeclarations.add(it) } + } + declaration.declarations.addAll(newDeclarations) + return super.visitClass(declaration) + } + + override fun visitProperty(declaration: IrProperty): IrStatement { + if (declaration.backingField != null) { + val backingField = declaration.backingField!! + if (backingField.initializer != null) { + val initializer = backingField.initializer!!.expression.transformAtomicValueInitializer(backingField) + declaration.backingField!!.initializer = context.irFactory.createExpressionBody(initializer) + } + } + return super.visitProperty(declaration) + } + + override fun visitFunction(declaration: IrFunction): IrStatement { + transformDeclarationBody(declaration.body, declaration) + return super.visitFunction(declaration) + } + + override fun visitAnonymousInitializer(declaration: IrAnonymousInitializer): IrStatement { + transformDeclarationBody(declaration.body, declaration) + return super.visitAnonymousInitializer(declaration) + } + + private fun transformDeclarationBody(body: IrBody?, parent: IrDeclaration, lambda: IrFunction? = null) { + if (body is IrBlockBody) { + body.statements.forEachIndexed { i, stmt -> + val transformedStmt = stmt.transformStatement(parent, lambda) + body.statements[i] = transformedStmt + if (transformedStmt is IrCall && transformedStmt.symbol.owner.name.asString().matches(ATOMICFU_INLINE_FUNCTION.toRegex())) { + val lambdaLoop = (transformedStmt.getValueArgument(0) as IrFunctionExpression).function + transformDeclarationBody(lambdaLoop.body, parent, lambdaLoop) + } + } + } + } + + private fun IrExpression.transformAtomicValueInitializer(parentDeclaration: IrDeclaration, lambda: IrFunction? = null) = + when { + type.isAtomicValueType() -> getPureTypeValue().transformAtomicFunctionCall(parentDeclaration, lambda) + type.isAtomicArrayType() -> buildPureTypeArrayConstructor() + type.isReentrantLockType() -> buildConstNull() + else -> this + } + + private fun IrDeclaration.transformAtomicInlineDeclaration(): IrDeclaration? { + // inline fun Atomic*<T>.foo(...) { ... } + if (this is IrFunction && + isInline && + extensionReceiverParameter != null && + extensionReceiverParameter!!.type.isAtomicValueType() + ) { + val extensionReceiverAtomicType = extensionReceiverParameter!!.type // Atomic*<T> + val extensionReceiverValueType = extensionReceiverAtomicType.atomicToValueType() // T + val extensionReceiverTypeParameter = extensionReceiverValueType.classifierOrNull?.owner + // containing declaration of this type parameter is transformed -> wrap it's type descriptor, so that MangleChecker will skip it + val wrappedExtensionReceiverType = extensionReceiverValueType.wrapTypeParameterDescriptor() + val getterType = buildGetterType(wrappedExtensionReceiverType) + val setterType = buildSetterType(wrappedExtensionReceiverType) + val valueParametersCount = valueParameters.size + val oldDeclaration = this + return buildFunction(parent, origin, name, visibility, isInline, returnType).apply { + body = oldDeclaration.body?.deepCopyWithSymbols(this) + val oldParameters = oldDeclaration.valueParameters.mapIndexed { index, p -> + val typeParameter = p.type.classifierOrNull?.owner + val wrappedType = if (typeParameter is IrClass) { + val arguments = (p.type as IrSimpleType).arguments.map { typeArg -> + if (typeArg is IrSimpleType && typeArg.classifier.owner === extensionReceiverTypeParameter) { + wrappedExtensionReceiverType as IrTypeArgument + } else typeArg + } + p.type.wrapClassTypeDescriptor(arguments) + } else p.type.wrapTypeParameterDescriptor() + buildValueParameter(this, p.name.identifier, index, wrappedType) + } + val extendedValueParameters = oldParameters + listOf( + buildValueParameter(this, GETTER, valueParametersCount, getterType), + buildValueParameter(this, SETTER, valueParametersCount + 1, setterType) + ) + valueParameters = extendedValueParameters + typeParameters = oldDeclaration.typeParameters.mapIndexed { i, t -> + buildTypeParameter(this) { + origin = t.origin + name = t.name + index = i + isReified = t.isReified + variance = t.variance + } + } + } + } + return null + } + + private fun IrType.wrapClassTypeDescriptor(typeArguments: List<IrTypeArgument>): IrType { + val typeParameter = classifierOrNull?.owner + return if (this is IrSimpleType && typeParameter is IrClass) { + val classifier = IrClassSymbolImpl().apply { bind(typeParameter) } + IrSimpleTypeImpl(classifier, hasQuestionMark, typeArguments, annotations, abbreviation) + } else this + } + + private fun IrType.wrapTypeParameterDescriptor(): IrType { + val typeParameter = classifierOrNull?.owner + return if (this is IrSimpleType && typeParameter is IrTypeParameter) { + val classifier = IrTypeParameterSymbolImpl().apply { bind(typeParameter) } + IrSimpleTypeImpl(classifier, hasQuestionMark, arguments, annotations, abbreviation) + } else this + } + + private fun IrExpression.getPureTypeValue(): IrExpression { + require(this is IrCall && isAtomicFactoryFunction()) { "Illegal initializer for the atomic property $this" } + return getValueArgument(0)!! + } + + private fun IrExpression.buildPureTypeArrayConstructor() = + when (this) { + is IrConstructorCall -> { + require(isAtomicArrayConstructor()) + val arrayConstructorSymbol = type.getArrayConstructorSymbol { it.owner.valueParameters.size == 1 } + val size = getValueArgument(0) + IrConstructorCallImpl( + UNDEFINED_OFFSET, UNDEFINED_OFFSET, + irBuiltIns.unitType, arrayConstructorSymbol, + 0, 0, 1 + ).apply { + putValueArgument(0, size) + } + } + is IrCall -> { + require(isAtomicArrayFactoryFunction()) { "Unsupported atomic array factory function $this" } + val arrayFactorySymbol = referencePackageFunction("kotlin", "arrayOfNulls") + val arrayElementType = getTypeArgument(0)!! + val size = getValueArgument(0) + buildCall( + target = arrayFactorySymbol, + type = type, + typeArguments = listOf(arrayElementType), + valueArguments = listOf(size) + ) + } + else -> error("Illegal type of atomic array initializer") + } + + private fun IrCall.runtimeInlineAtomicFunctionCall(atomicType: IrType, accessors: List<IrExpression>): IrCall { + val valueArguments = getValueArguments() + val functionName = getAtomicFunctionName() + val runtimeFunction = getRuntimeFunctionSymbol(functionName, atomicType) + return buildCall( + target = runtimeFunction, + type = type, + origin = IrStatementOrigin.INVOKE, + typeArguments = if (runtimeFunction.owner.typeParameters.size == 1) listOf(atomicType) else emptyList(), + valueArguments = valueArguments + accessors + ) + } + + private fun IrStatement.transformStatement(parentDeclaration: IrDeclaration, lambda: IrFunction? = null) = + when (this) { + is IrExpression -> transformAtomicFunctionCall(parentDeclaration, lambda) + is IrVariable -> { + apply { initializer = initializer?.transformAtomicFunctionCall(parentDeclaration, lambda) } + } + else -> this + } + + private fun IrExpression.transformAtomicFunctionCall(parentDeclaration: IrDeclaration, lambda: IrFunction? = null): IrExpression { + // erase unchecked cast to the Atomic* type + if (this is IrTypeOperatorCall && operator == IrTypeOperator.CAST && typeOperand.isAtomicValueType()) { + return argument + } + if (isAtomicValueInitializerCall()) { + return transformAtomicValueInitializer(parentDeclaration, lambda) + } + when (this) { + is IrTypeOperatorCall -> { + return apply { argument = argument.transformAtomicFunctionCall(parentDeclaration, lambda) } + } + is IrStringConcatenationImpl -> { + return apply { + arguments.forEachIndexed { i, arg -> + arguments[i] = arg.transformAtomicFunctionCall(parentDeclaration, lambda) + } + } + } + is IrReturn -> { + if (parentDeclaration is IrFunction && parentDeclaration.isInline && + parentDeclaration.hasReceiverAccessorParameters() && returnTargetSymbol !== parentDeclaration.symbol) { + return IrReturnImpl( + startOffset, + endOffset, + type, + parentDeclaration.symbol, + value.transformAtomicFunctionCall(parentDeclaration, lambda) + ) + } + return apply { value = value.transformAtomicFunctionCall(parentDeclaration, lambda) } + } + is IrSetValue -> { + return apply { value = value.transformAtomicFunctionCall(parentDeclaration, lambda) } + } + is IrSetField -> { + return apply { value = value.transformAtomicFunctionCall(parentDeclaration, lambda) } + } + is IrIfThenElseImpl -> { + return apply { + branches.forEachIndexed { i, branch -> + branches[i] = branch.apply { + condition = condition.transformAtomicFunctionCall(parentDeclaration, lambda) + result = result.transformAtomicFunctionCall(parentDeclaration, lambda) + } + } + } + } + is IrWhenImpl -> { + return apply { + branches.forEachIndexed { i, branch -> + branches[i] = branch.apply { + condition = condition.transformAtomicFunctionCall(parentDeclaration, lambda) + result = result.transformAtomicFunctionCall(parentDeclaration, lambda) + } + } + } + } + is IrTry -> { + return apply { + tryResult = tryResult.transformAtomicFunctionCall(parentDeclaration, lambda) + catches.forEach { + it.result = it.result.transformAtomicFunctionCall(parentDeclaration, lambda) + } + finallyExpression = finallyExpression?.transformAtomicFunctionCall(parentDeclaration, lambda) + } + } + is IrBlock -> { + return apply { + statements.forEachIndexed { i, stmt -> + statements[i] = stmt.transformStatement(parentDeclaration, lambda) + } + } + } + is IrGetValue -> { + if (lambda != null && symbol.owner.parent == lambda) return this + if (symbol is IrValueParameterSymbol && parentDeclaration.isTransformedAtomicExtensionFunction()) { + // replace use site of the value parameter with it's copy from the transformed declaration + val index = (symbol.owner as IrValueParameter).index + if (index >= 0) { // index == -1 for `this` parameter + val transformedValueParameter = (parentDeclaration as IrFunction).valueParameters[index] + return buildGetValue(transformedValueParameter.symbol) + } + } + } + is IrCall -> { + dispatchReceiver?.let { dispatchReceiver = it.transformAtomicFunctionCall(parentDeclaration, lambda) } + extensionReceiver?.let { extensionReceiver = it.transformAtomicFunctionCall(parentDeclaration, lambda) } + getValueArguments().forEachIndexed { i, arg -> + putValueArgument(i, arg?.transformAtomicFunctionCall(parentDeclaration, lambda)) + } + val isInline = symbol.owner.isInline + val field = extensionReceiver ?: dispatchReceiver ?: return this + if (symbol.isKotlinxAtomicfuPackage() && field.type.isAtomicValueType()) { // invocation of the atomic function + // 1. transform atomic function call on the atomic field + if (field is IrCall) { // property accessor <get-field> + val accessors = field.getPropertyAccessors(lambda ?: parentDeclaration) + return runtimeInlineAtomicFunctionCall(field.type.atomicToValueType(), accessors) + } + // 2. transform atomic function call on the atomic `this` extension receiver + // inline fun Atomic*.foo() { CAS(expect, update) } -> { atomicfu_compareAndSet(expect, update, getter, setter) } + if (field is IrGetValue && parentDeclaration.isTransformedAtomicExtensionFunction()) { + val accessorParameters = (parentDeclaration as IrFunction).valueParameters.takeLast(2).map { it.capture() } + return runtimeInlineAtomicFunctionCall(field.type.atomicToValueType(), accessorParameters) + } + } else { + // 3. transform invocation of an inline Atomic* extension function call: a.foo() + if (isInline && field is IrCall && field.type.isAtomicValueType()) { + val accessors = field.getPropertyAccessors(lambda ?: parentDeclaration) + val dispatch = dispatchReceiver + val args = getValueArguments() + val transformedTarget = symbol.owner.getDeclarationWithAccessorParameters() + return buildCall( + target = transformedTarget.symbol, + type = type, + origin = IrStatementOrigin.INVOKE, + valueArguments = args + accessors + ).apply { + dispatchReceiver = dispatch + } + } + } + } + is IrConstructorCall -> { + getValueArguments().forEachIndexed { i, arg -> + putValueArgument(i, arg?.transformAtomicFunctionCall(parentDeclaration, lambda)) + } + } + } + return this + } + + private fun IrFunction.hasReceiverAccessorParameters(): Boolean { + if (valueParameters.size < 2) return false + val params = valueParameters.takeLast(2) + return params[0].name.asString() == GETTER && params[1].name.asString() == SETTER + } + + private fun IrDeclaration.isTransformedAtomicExtensionFunction(): Boolean = + this is IrFunction && this.hasReceiverAccessorParameters() + + private fun IrFunction.getDeclarationWithAccessorParameters(): IrSimpleFunction { + val parent = parent as IrDeclarationContainer + val params = valueParameters.map { it.type } + val extensionType = extensionReceiverParameter?.type?.atomicToValueType() + return try { + parent.declarations.single { + it is IrSimpleFunction && + it.name == symbol.owner.name && + it.valueParameters.size == params.size + 2 && + it.valueParameters.dropLast(2).withIndex().all { p -> p.value.type.classifierOrNull?.owner == params[p.index].classifierOrNull?.owner } && + it.getGetterReturnType()?.classifierOrNull?.owner == extensionType?.classifierOrNull?.owner + } as IrSimpleFunction + } catch (e: RuntimeException) { + error("Exception while looking for the declaration with accessor parameters: ${e.message}") + } + } + + private fun IrExpression.isAtomicValueInitializerCall() = + (this is IrCall && (this.isAtomicFactoryFunction() || this.isAtomicArrayFactoryFunction())) || + (this is IrConstructorCall && this.isAtomicArrayConstructor()) || + type.isReentrantLockType() + + private fun IrCall.isArrayElementGetter() = + dispatchReceiver != null && + dispatchReceiver!!.type.isAtomicArrayType() && + symbol.owner.name.asString() == GET + + private fun IrCall.getBackingField(): IrField { + val correspondingPropertySymbol = symbol.owner.correspondingPropertySymbol!! + return correspondingPropertySymbol.owner.backingField!! + } + + private fun IrCall.buildAccessorLambda( + isSetter: Boolean, + parentDeclaration: IrDeclaration + ): IrFunctionExpression { + val isArrayElement = isArrayElementGetter() + val getterCall = if (isArrayElement) dispatchReceiver as IrCall else this + val valueType = type.atomicToValueType() + val type = if (isSetter) buildSetterType(valueType) else buildGetterType(valueType) + val name = if (isSetter) setterName(getterCall.symbol.owner.name.getFieldName()) + else getterName(getterCall.symbol.owner.name.getFieldName()) + val returnType = if (isSetter) context.irBuiltIns.unitType else valueType + val accessorFunction = buildFunction( + parent = parentDeclaration as IrDeclarationParent, + origin = IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR, + name = Name.identifier(name), + visibility = DescriptorVisibilities.LOCAL, + isInline = true, + returnType = returnType + ).apply { + val valueParameter = buildValueParameter(this, name, 0, valueType) + this.valueParameters = if (isSetter) listOf(valueParameter) else emptyList() + val body = if (isSetter) { + if (isArrayElement) { + val setSymbol = referenceFunction(getterCall.type.referenceClass(), SET) + val elementIndex = getValueArgument(0)!!.deepCopyWithVariables() + buildCall( + target = setSymbol, + type = context.irBuiltIns.unitType, + origin = IrStatementOrigin.LAMBDA, + valueArguments = listOf(elementIndex, valueParameter.capture()) + ).apply { + dispatchReceiver = getterCall + } + } else { + buildSetField(getterCall.getBackingField(), getterCall.dispatchReceiver, valueParameter.capture()) + } + } else { + val getField = buildGetField(getterCall.getBackingField(), getterCall.dispatchReceiver) + if (isArrayElement) { + val getSymbol = referenceFunction(getterCall.type.referenceClass(), GET) + val elementIndex = getValueArgument(0)!!.deepCopyWithVariables() + buildCall( + target = getSymbol, + type = valueType, + origin = IrStatementOrigin.LAMBDA, + valueArguments = listOf(elementIndex) + ).apply { + dispatchReceiver = getField.deepCopyWithVariables() + } + } else { + getField.deepCopyWithVariables() + } + } + this.body = buildBlockBody(listOf(body)) + origin = IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR + } + return IrFunctionExpressionImpl( + UNDEFINED_OFFSET, UNDEFINED_OFFSET, + type, + accessorFunction, + IrStatementOrigin.LAMBDA + ) + } + + private fun buildSetField(backingField: IrField, ownerClass: IrExpression?, value: IrGetValue): IrSetField { + val receiver = if (ownerClass is IrTypeOperatorCall) ownerClass.argument as IrGetValue else ownerClass + val fieldSymbol = backingField.symbol + return buildSetField( + symbol = fieldSymbol, + receiver = receiver, + value = value + ) + } + + private fun buildGetField(backingField: IrField, ownerClass: IrExpression?): IrGetField { + val receiver = if (ownerClass is IrTypeOperatorCall) ownerClass.argument as IrGetValue else ownerClass + return buildGetField(backingField.symbol, receiver) + } + + private fun IrCall.getPropertyAccessors(parentDeclaration: IrDeclaration): List<IrExpression> = + listOf(buildAccessorLambda(isSetter = false, parentDeclaration = parentDeclaration), + buildAccessorLambda(isSetter = true, parentDeclaration = parentDeclaration)) + + private fun getRuntimeFunctionSymbol(name: String, type: IrType): IrSimpleFunctionSymbol { + val functionName = when (name) { + "value.<get-value>" -> "getValue" + "value.<set-value>" -> "setValue" + else -> name + } + return referencePackageFunction("kotlinx.atomicfu", "$ATOMICFU_RUNTIME_FUNCTION_PREDICATE$functionName") { + val typeArg = it.owner.getGetterReturnType() + !(typeArg as IrType).isPrimitiveType() || typeArg == type + } + } + + private fun IrFunction.getGetterReturnType() = (valueParameters[valueParameters.lastIndex - 1].type as IrSimpleType).arguments.first().typeOrNull + + private fun IrCall.isAtomicFactoryFunction(): Boolean { + val name = symbol.owner.name + return !name.isSpecial && name.identifier == ATOMIC_CONSTRUCTOR + } + + private fun IrCall.isAtomicArrayFactoryFunction(): Boolean { + val name = symbol.owner.name + return !name.isSpecial && name.identifier == ATOMIC_ARRAY_FACTORY_FUNCTION + } + + private fun IrConstructorCall.isAtomicArrayConstructor() = (type as IrSimpleType).isAtomicArrayType() + + private fun IrSymbol.isKotlinxAtomicfuPackage() = + this.isPublicApi && signature?.packageFqName()?.asString() == AFU_PKG.prettyStr() + + private fun IrType.isAtomicValueType() = belongsTo(ATOMICFU_VALUE_TYPE) + private fun IrType.isAtomicArrayType() = belongsTo(ATOMIC_ARRAY_TYPE) + private fun IrType.isReentrantLockType() = belongsTo("$AFU_PKG/$LOCKS", REENTRANT_LOCK_TYPE) + + private fun IrType.belongsTo(typeName: String) = belongsTo(AFU_PKG, typeName) + + private fun IrType.belongsTo(packageName: String, typeName: String): Boolean { + if (this !is IrSimpleType || !(classifier.isPublicApi && classifier is IrClassSymbol)) return false + val signature = classifier.signature?.asPublic() ?: return false + val pckg = signature.packageFqName().asString() + val type = signature.declarationFqName + return pckg == packageName.prettyStr() && type.matches(typeName.toRegex()) + } + + private fun IrCall.getAtomicFunctionName(): String { + val signature = symbol.signature!! + val classFqName = if (signature is IdSignature.AccessorSignature) { + signature.accessorSignature.declarationFqName + } else (signature.asPublic()!!).declarationFqName + val pattern = "$ATOMICFU_VALUE_TYPE\\.(.*)".toRegex() + return pattern.findAll(classFqName).firstOrNull()?.let { it.groupValues[2] } ?: classFqName + } + + private fun IrType.atomicToValueType(): IrType { + require(isAtomicValueType()) + val classId = ((this as IrSimpleType).classifier.signature as IdSignature.PublicSignature).declarationFqName + if (classId == "AtomicRef") { + return arguments.first().typeOrNull ?: error("$AFU_PKG/AtomicRef type parameter is not IrTypeProjection") + } + return AFU_CLASSES[classId] ?: error("IrType ${this.getClass()} does not match any of atomicfu types") + } + + private fun buildConstNull() = IrConstImpl.constNull(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.anyType) + + private fun IrType.getArrayConstructorSymbol(predicate: (IrConstructorSymbol) -> Boolean = { true }): IrConstructorSymbol { + val afuClassId = ((this as IrSimpleType).classifier.signature as IdSignature.PublicSignature).declarationFqName + val classId = FqName("$KOTLIN.${AFU_ARRAY_CLASSES[afuClassId]!!}") + return context.referenceConstructors(classId).single(predicate) + } + + private fun IrType.referenceClass(): IrClassSymbol { + val afuClassId = ((this as IrSimpleType).classifier.signature as IdSignature.PublicSignature).declarationFqName + val classId = FqName("$KOTLIN.${AFU_ARRAY_CLASSES[afuClassId]!!}") + return context.referenceClass(classId)!! + } + + private fun referencePackageFunction( + packageName: String, + name: String, + predicate: (IrFunctionSymbol) -> Boolean = { true } + ) = try { + context.referenceFunctions(FqName("$packageName.$name")).single(predicate) + } catch (e: RuntimeException) { + error("Exception while looking for the function `$name` in package `$packageName`: ${e.message}") + } + + private fun referenceFunction(classSymbol: IrClassSymbol, functionName: String): IrSimpleFunctionSymbol { + val functionId = FqName("$KOTLIN.${classSymbol.owner.name}.$functionName") + return try { + context.referenceFunctions(functionId).single() + } catch (e: RuntimeException) { + error("Exception while looking for the function `$functionId`: ${e.message}") + } + } + + companion object { + fun transform(irFile: IrFile, context: IrPluginContext) = + irFile.transform(AtomicFUTransformer(context), null) + } +}
diff --git a/plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/AtomicfuComponentRegistrar.kt b/plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/AtomicfuComponentRegistrar.kt new file mode 100644 index 0000000..b33555f --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/AtomicfuComponentRegistrar.kt
@@ -0,0 +1,34 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlinx.atomicfu.compiler.extensions + +import com.intellij.mock.MockProject +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension +import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar +import org.jetbrains.kotlin.config.CompilerConfiguration + +class AtomicfuComponentRegistrar : ComponentRegistrar { + override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) { + registerExtensions(project) + } + + companion object { + fun registerExtensions(project: Project) { + IrGenerationExtension.registerExtension(project, AtomicfuLoweringExtension()) } + } +}
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 new file mode 100644 index 0000000..8191bee --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/AtomicfuLoweringExtension.kt
@@ -0,0 +1,54 @@ +/* + * Copyright 2010-2018 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.extensions + +import org.jetbrains.kotlin.backend.common.FileLoweringPass +import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext +import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension +import org.jetbrains.kotlin.backend.common.runOnFilePostfix +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid +import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid +import org.jetbrains.kotlin.ir.visitors.acceptVoid +import java.lang.IllegalStateException + +public open class AtomicfuLoweringExtension : IrGenerationExtension { + override fun generate( + moduleFragment: IrModuleFragment, + pluginContext: IrPluginContext + ) { + val atomicfuClassLowering = AtomicfuClassLowering(pluginContext) + for (file in moduleFragment.files) { + atomicfuClassLowering.runOnFileInOrder(file) + } + } +} + +/** + * Copy of [runOnFilePostfix], but this implementation first lowers declaration, then its children. + */ +fun FileLoweringPass.runOnFileInOrder(irFile: IrFile) { + irFile.acceptVoid(object : IrElementVisitorVoid { + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + } + + override fun visitFile(declaration: IrFile) { + lower(declaration) + declaration.acceptChildrenVoid(this) + } + }) +} + +private class AtomicfuClassLowering( + val context: IrPluginContext +) : IrElementTransformerVoid(), FileLoweringPass { + override fun lower(irFile: IrFile) { + AtomicFUTransformer.transform(irFile, context) + } +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/TransformerHelpers.kt b/plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/TransformerHelpers.kt new file mode 100644 index 0000000..cf11af1 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/TransformerHelpers.kt
@@ -0,0 +1,136 @@ +/* + * Copyright 2010-2020 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.extensions + +import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext +import org.jetbrains.kotlin.descriptors.DescriptorVisibility +import org.jetbrains.kotlin.descriptors.Visibility +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET +import org.jetbrains.kotlin.ir.builders.declarations.buildFun +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrGetFieldImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrSetFieldImpl +import org.jetbrains.kotlin.ir.symbols.* +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl +import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.types.Variance + +interface TransformerHelpers { + + val context: IrPluginContext + + fun buildFunction( + parent: IrDeclarationParent, + origin: IrDeclarationOrigin, + name: Name, + visibility: DescriptorVisibility, + isInline: Boolean, + returnType: IrType + ): IrSimpleFunction = + context.irFactory.buildFun { + startOffset = UNDEFINED_OFFSET + endOffset = UNDEFINED_OFFSET + this.origin = origin + this.name = name + this.visibility = visibility + this.isInline = isInline + this.returnType = returnType + }.apply { + this.parent = parent + } + + fun buildCall( + target: IrSimpleFunctionSymbol, + type: IrType? = null, + origin: IrStatementOrigin? = null, + typeArguments: List<IrType> = emptyList(), + valueArguments: List<IrExpression?> = emptyList() + ): IrCall = + IrCallImpl( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + type ?: target.owner.returnType, + target, + typeArguments.size, + valueArguments.size, + origin + ).apply { + typeArguments.let { + it.withIndex().forEach { (i, t) -> putTypeArgument(i, t) } + } + valueArguments.let { + it.withIndex().forEach { (i, arg) -> putValueArgument(i, arg) } + } + } + + fun buildBlockBody(statements: List<IrStatement>) = + context.irFactory.createBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET, statements) + + fun buildSetField( + symbol: IrFieldSymbol, + receiver: IrExpression?, + value: IrExpression, + superQualifierSymbol: IrClassSymbol? = null + ) = + IrSetFieldImpl( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + symbol, + receiver, + value, + value.type, + IrStatementOrigin.GET_PROPERTY, + superQualifierSymbol + ) + + fun buildGetField(symbol: IrFieldSymbol, receiver: IrExpression?, superQualifierSymbol: IrClassSymbol? = null, type: IrType? = null) = + IrGetFieldImpl( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + symbol, + type ?: symbol.owner.type, + receiver, + IrStatementOrigin.GET_PROPERTY, + superQualifierSymbol + ) + + fun buildFunctionSimpleType(paramsCount: Int, typeParameters: List<IrType>): IrSimpleType { + val classSymbol = context.irBuiltIns.function(paramsCount) + return IrSimpleTypeImpl( + classifier = classSymbol, + hasQuestionMark = false, + arguments = typeParameters.map { it.toTypeArgument() }, + annotations = emptyList() + ) + } + + fun buildGetterType(valueType: IrType) = buildFunctionSimpleType(0, listOf(valueType)) + fun buildSetterType(valueType: IrType) = buildFunctionSimpleType(1, listOf(valueType, context.irBuiltIns.unitType)) + + fun buildGetValue(symbol: IrValueSymbol) = + IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, symbol.owner.type, symbol) + + fun getterName(name: String) = "<get-$name>" + fun setterName(name: String) = "<set-$name>" + fun Name.getFieldName() = "<get-(\\w+)>".toRegex().find(asString())?.groupValues?.get(1) + ?: error("Getter name ${this.asString()} does not match special name pattern <get-fieldName>") + + private fun IrType.toTypeArgument(): IrTypeArgument { + return makeTypeProjection(this, Variance.INVARIANT) + } + + fun IrFunctionAccessExpression.getValueArguments() = (0 until valueArgumentsCount).map { i -> + getValueArgument(i) + } + + fun IrValueParameter.capture() = buildGetValue(symbol) +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/test/org/jetbrains/kotlinx/atomicfu/BasicAtomicfuTestGenerated.java b/plugins/atomicfu/atomicfu-compiler/test/org/jetbrains/kotlinx/atomicfu/BasicAtomicfuTestGenerated.java new file mode 100644 index 0000000..526f348 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/test/org/jetbrains/kotlinx/atomicfu/BasicAtomicfuTestGenerated.java
@@ -0,0 +1,121 @@ +/* + * Copyright 2010-2020 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; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.util.KtTestUtil; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("plugins/atomicfu/atomicfu-compiler/testData/basic") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class BasicAtomicfuTestGenerated extends AbstractBasicAtomicfuTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInBasic() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/atomicfu/atomicfu-compiler/testData/basic"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("ArithmeticTest.kt") + public void testArithmeticTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/basic/ArithmeticTest.kt"); + } + + @TestMetadata("AtomicArrayTest.kt") + public void testAtomicArrayTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/basic/AtomicArrayTest.kt"); + } + + @TestMetadata("ArrayInlineFunctionTest.kt") + public void testArrayInlineFunctionTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/basic/ArrayInlineFunctionTest.kt"); + } + + @TestMetadata("ExtensionsTest.kt") + public void testExtensionsTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/basic/ExtensionsTest.kt"); + } + + @TestMetadata("IndexArrayElementGetterTest.kt") + public void testIndexArrayElementGetterTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/basic/IndexArrayElementGetterTest.kt"); + } + + @TestMetadata("LockFreeStackTest.kt") + public void testLockFreeStackTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/basic/LockFreeStackTest.kt"); + } + + @TestMetadata("LockFreeQueueTest.kt") + public void testLockFreeQueueTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/basic/LockFreeQueueTest.kt"); + } + + @TestMetadata("LockFreeLongCounterTest.kt") + public void testLockFreeLongCounterTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/basic/LockFreeLongCounterTest.kt"); + } + + @TestMetadata("LockFreeIntBitsTest.kt") + public void testLockFreeIntBitsTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/basic/LockFreeIntBitsTest.kt"); + } + + @TestMetadata("LoopTest.kt") + public void testLoopTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/basic/LoopTest.kt"); + } + + @TestMetadata("ScopeTest.kt") + public void testScopeTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/basic/ScopeTest.kt"); + } + + @TestMetadata("LockTest.kt") + public void testLockTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/basic/LockTest.kt"); + } + + @TestMetadata("MultiInitTest.kt") + public void testMultiInitTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/basic/MultiInitTest.kt"); + } + + @TestMetadata("SimpleLockTest.kt") + public void testSimpleLockTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/basic/SimpleLockTest.kt"); + } + + @TestMetadata("TopLevelTest.kt") + public void testTopLevelTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/basic/TopLevelTest.kt"); + } + + @TestMetadata("UncheckedCastTest.kt") + public void testUncheckedCastTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/basic/UncheckedCastTest.kt"); + } + + @TestMetadata("PropertyDeclarationTest.kt") + public void testPropertyDeclarationTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/basic/PropertyDeclarationTest.kt"); + } + + @TestMetadata("ParameterizedInlineFunExtensionTest.kt") + public void testParameterizedInlineFunExtensionTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/basic/ParameterizedInlineFunExtensionTest.kt"); + } +}
diff --git a/plugins/atomicfu/atomicfu-compiler/test/org/jetbrains/kotlinx/atomicfu/JsGenerateAndRunTest.kt b/plugins/atomicfu/atomicfu-compiler/test/org/jetbrains/kotlinx/atomicfu/JsGenerateAndRunTest.kt new file mode 100644 index 0000000..f626a25 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/test/org/jetbrains/kotlinx/atomicfu/JsGenerateAndRunTest.kt
@@ -0,0 +1,35 @@ +/* + * Copyright 2010-2020 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 + +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.js.config.JSConfigurationKeys +import org.jetbrains.kotlin.js.test.BasicBoxTest +import org.jetbrains.kotlin.js.test.BasicIrBoxTest +import org.jetbrains.kotlin.serialization.js.ModuleKind +import org.jetbrains.kotlin.test.KotlinTestUtils +import org.jetbrains.kotlinx.atomicfu.compiler.extensions.AtomicfuComponentRegistrar +import org.junit.Test +import java.io.File + +private val atomicfuCompileDependency = System.getProperty("atomicfu.classpath") +private val atomicfuRuntime = System.getProperty("atomicfuRuntimeForTests.classpath") + +abstract class AtomicfuBaseTest(relativePath: String) : BasicIrBoxTest( + "plugins/atomicfu/atomicfu-compiler/testData/$relativePath", + "plugins/atomicfu/atomicfu-compiler/testData/$relativePath" +) { + override fun createEnvironment(): KotlinCoreEnvironment { + return super.createEnvironment().also { environment -> + AtomicfuComponentRegistrar.registerExtensions(environment.project) + environment.configuration.put(JSConfigurationKeys.LIBRARIES, listOf(atomicfuCompileDependency, atomicfuRuntime)) + environment.configuration.put(JSConfigurationKeys.MODULE_KIND, ModuleKind.PLAIN) + } + } +} + +abstract class AbstractBasicAtomicfuTest : AtomicfuBaseTest("basic/") +abstract class AbstractLocksAtomicfuTest : AtomicfuBaseTest("locks/") \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/test/org/jetbrains/kotlinx/atomicfu/LocksAtomicfuTestGenerated.java b/plugins/atomicfu/atomicfu-compiler/test/org/jetbrains/kotlinx/atomicfu/LocksAtomicfuTestGenerated.java new file mode 100644 index 0000000..04016d5 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/test/org/jetbrains/kotlinx/atomicfu/LocksAtomicfuTestGenerated.java
@@ -0,0 +1,41 @@ +/* + * Copyright 2010-2020 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; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.util.KtTestUtil; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("plugins/atomicfu/atomicfu-compiler/testData/locks") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class LocksAtomicfuTestGenerated extends AbstractLocksAtomicfuTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInLocks() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/atomicfu/atomicfu-compiler/testData/locks"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("ReentrantLockTest.kt") + public void testReentrantLockTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/locks/ReentrantLockTest.kt"); + } + + @TestMetadata("SynchronizedObjectTest.kt") + public void testSynchronizedObjectTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/locks/SynchronizedObjectTest.kt"); + } +}
diff --git a/plugins/atomicfu/atomicfu-compiler/testData/basic/ArithmeticTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/basic/ArithmeticTest.kt new file mode 100644 index 0000000..c9e3a63 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/basic/ArithmeticTest.kt
@@ -0,0 +1,131 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +class IntArithmetic { + val _x = atomic(0) + val x get() = _x.value +} + +class LongArithmetic { + val _x = atomic(4294967296) + val x get() = _x.value + val y = atomic(5000000000) + val z = atomic(2424920024888888848) + val max = atomic(9223372036854775807) +} + +class BooleanArithmetic { + val _x = atomic(false) + val x get() = _x.value +} + +class ReferenceArithmetic { + val _x = atomic<String?>(null) +} + +class ArithmeticTest { + val local = atomic(0) + + fun testGetValue() { + val a = IntArithmetic() + a._x.value = 5 + check(a._x.value == 5) + var aValue = a._x.value + check(aValue == 5) + check(a.x == 5) + + local.value = 555 + aValue = local.value + check(local.value == aValue) + } + + fun testAtomicCallPlaces(): Boolean { + val a = IntArithmetic() + a._x.value = 5 + a._x.compareAndSet(5, 42) + val res = a._x.compareAndSet(42, 45) + check(res) + check(a._x.compareAndSet(45, 77)) + check(!a._x.compareAndSet(95, 77)) + return a._x.compareAndSet(77, 88) + } + + fun testInt() { + val a = IntArithmetic() + check(a.x == 0) + val update = 3 + check(a._x.getAndSet(update) == 0) + check(a._x.compareAndSet(update, 8)) + a._x.lazySet(1) + check(a.x == 1) + check(a._x.getAndSet(2) == 1) + check(a.x == 2) + check(a._x.getAndIncrement() == 2) + check(a.x == 3) + check(a._x.getAndDecrement() == 3) + check(a.x == 2) + check(a._x.getAndAdd(2) == 2) + check(a.x == 4) + check(a._x.addAndGet(3) == 7) + check(a.x == 7) + check(a._x.incrementAndGet() == 8) + check(a.x == 8) + check(a._x.decrementAndGet() == 7) + check(a.x == 7) + check(a._x.compareAndSet(7, 10)) + } + + fun testLong() { + val a = LongArithmetic() + check(a.z.value == 2424920024888888848) + a.z.lazySet(8424920024888888848) + check(a.z.value == 8424920024888888848) + check(a.z.getAndSet(8924920024888888848) == 8424920024888888848) + check(a.z.value == 8924920024888888848) + check(a.z.incrementAndGet() == 8924920024888888849) // fails + check(a.z.value == 8924920024888888849) + check(a.z.getAndDecrement() == 8924920024888888849) + check(a.z.value == 8924920024888888848) + check(a.z.getAndAdd(100000000000000000) == 8924920024888888848) + check(a.z.value == 9024920024888888848) + check(a.z.addAndGet(-9223372036854775807) == -198452011965886959) + check(a.z.value == -198452011965886959) + check(a.z.incrementAndGet() == -198452011965886958) + check(a.z.value == -198452011965886958) + check(a.z.decrementAndGet() == -198452011965886959) + check(a.z.value == -198452011965886959) + } + + fun testBoolean() { + val a = BooleanArithmetic() + check(!a.x) + a._x.lazySet(true) + check(a.x) + check(a._x.getAndSet(true)) + check(a._x.compareAndSet(true, false)) + check(!a.x) + } + + fun testReference() { + val a = ReferenceArithmetic() + a._x.value = "aaa" + check(a._x.value == "aaa") + a._x.lazySet("bb") + check(a._x.value == "bb") + check(a._x.getAndSet("ccc") == "bb") + check(a._x.value == "ccc") + } +} + +fun box(): String { + val testClass = ArithmeticTest() + + testClass.testGetValue() + if (!testClass.testAtomicCallPlaces()) return "testAtomicCallPlaces: FAILED" + + testClass.testInt() + testClass.testLong() + testClass.testBoolean() + testClass.testReference() + return "OK" +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/testData/basic/ArrayInlineFunctionTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/basic/ArrayInlineFunctionTest.kt new file mode 100644 index 0000000..c9d0b1d --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/basic/ArrayInlineFunctionTest.kt
@@ -0,0 +1,47 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +class ArrayInlineFunctionTest { + private val anyArr = atomicArrayOfNulls<Any?>(5) + private val refArr = atomicArrayOfNulls<Box>(5) + + private data class Box(val n: Int) + + fun testSetArrayElementValueInLoop() { + anyArr[0].loop { cur -> + assertTrue(anyArr[0].compareAndSet(cur, IntArray(5))) + return + } + } + + private fun action(cur: Box?) = cur?.let { Box(cur.n * 10) } + + + fun testArrayElementUpdate() { + refArr[0].lazySet(Box(5)) + refArr[0].update { cur -> cur?.let { Box(cur.n * 10) } } + assertEquals(refArr[0].value!!.n, 50) + } + + + fun testArrayElementGetAndUpdate() { + refArr[0].lazySet(Box(5)) + assertEquals(refArr[0].getAndUpdate { cur -> action(cur) }!!.n, 5) + assertEquals(refArr[0].value!!.n, 50) + } + + + fun testArrayElementUpdateAndGet() { + refArr[0].lazySet(Box(5)) + assertEquals(refArr[0].updateAndGet { cur -> action(cur) }!!.n, 50) + } +} + +fun box(): String { + val testClass = ArrayInlineFunctionTest() + testClass.testSetArrayElementValueInLoop() + testClass.testArrayElementGetAndUpdate() + testClass.testArrayElementUpdate() + testClass.testArrayElementUpdateAndGet() + return "OK" +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/testData/basic/AtomicArrayTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/basic/AtomicArrayTest.kt new file mode 100644 index 0000000..70e23e4 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/basic/AtomicArrayTest.kt
@@ -0,0 +1,97 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +class AtomicArrayTest { + + fun testIntArray() { + val A = AtomicArrayClass() + check(A.intArr[0].compareAndSet(0, 3)) + check(A.intArr[1].value == 0) + A.intArr[0].lazySet(5) + check(A.intArr[0].value + A.intArr[1].value + A.intArr[2].value == 5) + check(A.intArr[0].compareAndSet(5, 10)) + check(A.intArr[0].getAndDecrement() == 10) + check(A.intArr[0].value == 9) + A.intArr[2].value = 2 + check(A.intArr[2].value == 2) + check(A.intArr[2].compareAndSet(2, 34)) + check(A.intArr[2].value == 34) + } + + + fun testLongArray() { + val A = AtomicArrayClass() + A.longArr[0].value = 2424920024888888848 + check(A.longArr[0].value == 2424920024888888848) + A.longArr[0].lazySet(8424920024888888848) + check(A.longArr[0].value == 8424920024888888848) + val ac = A.longArr[0].value + A.longArr[3].value = ac + check(A.longArr[3].getAndSet(8924920024888888848) == 8424920024888888848) + check(A.longArr[3].value == 8924920024888888848) + val ac1 = A.longArr[3].value + A.longArr[4].value = ac1 + check(A.longArr[4].incrementAndGet() == 8924920024888888849) + check(A.longArr[4].value == 8924920024888888849) + check(A.longArr[4].getAndDecrement() == 8924920024888888849) + check(A.longArr[4].value == 8924920024888888848) + A.longArr[4].value = 8924920024888888848 + check(A.longArr[4].getAndAdd(100000000000000000) == 8924920024888888848) + val ac2 = A.longArr[4].value + A.longArr[1].value = ac2 + check(A.longArr[1].value == 9024920024888888848) + check(A.longArr[1].addAndGet(-9223372036854775807) == -198452011965886959) + check(A.longArr[1].value == -198452011965886959) + check(A.longArr[1].incrementAndGet() == -198452011965886958) + check(A.longArr[1].value == -198452011965886958) + check(A.longArr[1].decrementAndGet() == -198452011965886959) + check(A.longArr[1].value == -198452011965886959) + } + + + fun testBooleanArray() { + val A = AtomicArrayClass() + check(!A.booleanArr[1].value) + A.booleanArr[1].compareAndSet(false, true) + A.booleanArr[0].lazySet(true) + check(!A.booleanArr[2].getAndSet(true)) + check(A.booleanArr[0].value && A.booleanArr[1].value && A.booleanArr[2].value) + A.booleanArr[0].value = false + check(!A.booleanArr[0].value) + } + + fun testRefArray() { + val A = AtomicArrayClass() + val a2 = ARef(2) + val a3 = ARef(3) + A.refArr[0].value = a2 + check(A.refArr[0].value!!.n == 2) + check(A.refArr[0].compareAndSet(a2, a3)) + check(A.refArr[0].value!!.n == 3) + val r0 = A.refArr[0].value + A.refArr[3].value = r0 + check(A.refArr[3].value!!.n == 3) + val a = A.a.value + check(A.refArr[3].compareAndSet(a3, a)) + } +} + +class AtomicArrayClass { + val intArr = AtomicIntArray(10) + val longArr = AtomicLongArray(10) + val booleanArr = AtomicBooleanArray(10) + val refArr = atomicArrayOfNulls<ARef>(10) + val anyArr = atomicArrayOfNulls<Any?>(10) + val a = atomic(ARef(8)) +} + +data class ARef(val n: Int) + +fun box(): String { + val testClass = AtomicArrayTest() + testClass.testIntArray() + testClass.testLongArray() + testClass.testBooleanArray() + testClass.testRefArray() + return "OK" +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/testData/basic/ExtensionsTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/basic/ExtensionsTest.kt new file mode 100644 index 0000000..510f90d --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/basic/ExtensionsTest.kt
@@ -0,0 +1,115 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +class ExtensionsTest { + val a = atomic(0) + val l = atomic(0L) + val s = atomic<String?>(null) + val b = atomic(true) + + fun testScopedFieldGetters() { + check(a.value == 0) + val update = 3 + a.lazySet(update) + check(a.compareAndSet(update, 8)) + a.lazySet(1) + check(a.value == 1) + check(a.getAndSet(2) == 1) + check(a.value == 2) + check(a.getAndIncrement() == 2) + check(a.value == 3) + check(a.getAndDecrement() == 3) + check(a.value == 2) + check(a.getAndAdd(2) == 2) + check(a.value == 4) + check(a.addAndGet(3) == 7) + check(a.value == 7) + check(a.incrementAndGet() == 8) + check(a.value == 8) + check(a.decrementAndGet() == 7) + check(a.value == 7) + check(a.compareAndSet(7, 10)) + } + + inline fun AtomicInt.intExtensionArithmetic() { + value = 0 + check(value == 0) + val update = 3 + lazySet(update) + check(compareAndSet(update, 8)) + lazySet(1) + check(value == 1) + check(getAndSet(2) == 1) + check(value == 2) + check(getAndIncrement() == 2) + check(value == 3) + check(getAndDecrement() == 3) + check(value == 2) + check(getAndAdd(2) == 2) + check(value == 4) + check(addAndGet(3) == 7) + check(value == 7) + check(incrementAndGet() == 8) + check(value == 8) + check(decrementAndGet() == 7) + check(value == 7) + check(compareAndSet(7, 10)) + check(compareAndSet(value, 55)) + check(value == 55) + } + + inline fun AtomicLong.longExtensionArithmetic() { + value = 2424920024888888848 + check(value == 2424920024888888848) + lazySet(8424920024888888848) + check(value == 8424920024888888848) + check(getAndSet(8924920024888888848) == 8424920024888888848) + check(value == 8924920024888888848) + check(incrementAndGet() == 8924920024888888849) // fails + check(value == 8924920024888888849) + check(getAndDecrement() == 8924920024888888849) + check(value == 8924920024888888848) + check(getAndAdd(100000000000000000) == 8924920024888888848) + check(value == 9024920024888888848) + check(addAndGet(-9223372036854775807) == -198452011965886959) + check(value == -198452011965886959) + check(incrementAndGet() == -198452011965886958) + check(value == -198452011965886958) + check(decrementAndGet() == -198452011965886959) + check(value == -198452011965886959) + } + + inline fun AtomicRef<String?>.refExtension() { + value = "aaa" + check(value == "aaa") + lazySet("bb") + check(value == "bb") + check(getAndSet("ccc") == "bb") + check(value == "ccc") + } + + inline fun AtomicBoolean.booleanExtensionArithmetic() { + value = false + check(!value) + lazySet(true) + check(value) + check(getAndSet(true)) + check(compareAndSet(value, false)) + check(!value) + } + + fun testExtension() { + a.intExtensionArithmetic() + l.longExtensionArithmetic() + s.refExtension() + b.booleanExtensionArithmetic() + } +} + + +fun box(): String { + val testClass = ExtensionsTest() + testClass.testScopedFieldGetters() + testClass.testExtension() + return "OK" +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/testData/basic/IndexArrayElementGetterTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/basic/IndexArrayElementGetterTest.kt new file mode 100644 index 0000000..69254e0 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/basic/IndexArrayElementGetterTest.kt
@@ -0,0 +1,32 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +class IndexArrayElementGetterTest { + private val clazz = AtomicArrayClass() + + fun fib(a: Int): Int = if (a == 0 || a == 1) a else fib(a - 1) + fib(a - 2) + + fun testIndexArrayElementGetting() { + clazz.intArr[8].value = 3 + val i = fib(4) + val j = fib(5) + assertEquals(clazz.intArr[i + j].value, 3) + assertEquals(clazz.intArr[fib(4) + fib(5)].value, 3) + clazz.longArr[3].value = 100 + assertEquals(clazz.longArr[fib(6) - fib(5)].value, 100) + assertEquals(clazz.longArr[(fib(6) + fib(4)) % 8].value, 100) + assertEquals(clazz.longArr[(fib(6) + fib(4)) % 8].value, 100) + assertEquals(clazz.longArr[(fib(4) + fib(5)) % fib(5)].value, 100) + } + + class AtomicArrayClass { + val intArr = AtomicIntArray(10) + val longArr = AtomicLongArray(10) + } +} + +fun box(): String { + val testClass = IndexArrayElementGetterTest() + testClass.testIndexArrayElementGetting() + return "OK" +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/testData/basic/LockFreeIntBitsTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/basic/LockFreeIntBitsTest.kt new file mode 100644 index 0000000..78d84a9 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/basic/LockFreeIntBitsTest.kt
@@ -0,0 +1,57 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +class LockFreeIntBitsTest { + fun testBasic() { + val bs = LockFreeIntBits() + check(!bs[0]) + check(bs.bitSet(0)) + check(bs[0]) + check(!bs.bitSet(0)) + + check(!bs[1]) + check(bs.bitSet(1)) + check(bs[1]) + check(!bs.bitSet(1)) + check(!bs.bitSet(0)) + + check(bs[0]) + check(bs.bitClear(0)) + check(!bs.bitClear(0)) + + check(bs[1]) + } +} + +class LockFreeIntBits { + private val bits = atomic(0) + + private fun Int.mask() = 1 shl this + + operator fun get(index: Int): Boolean = bits.value and index.mask() != 0 + + // User-defined private inline function + private inline fun bitUpdate(check: (Int) -> Boolean, upd: (Int) -> Int): Boolean { + bits.update { + if (check(it)) return false + upd(it) + } + return true + } + + fun bitSet(index: Int): Boolean { + val mask = index.mask() + return bitUpdate({ it and mask != 0 }, { it or mask }) + } + + fun bitClear(index: Int): Boolean { + val mask = index.mask() + return bitUpdate({ it and mask == 0 }, { it and mask.inv() }) + } +} + +fun box(): String { + val testClass = LockFreeIntBitsTest() + testClass.testBasic() + return "OK" +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/testData/basic/LockFreeLongCounterTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/basic/LockFreeLongCounterTest.kt new file mode 100644 index 0000000..853ca9e --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/basic/LockFreeLongCounterTest.kt
@@ -0,0 +1,61 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +class LockFreeLongCounterTest { + private inline fun testWith(g: LockFreeLongCounter.() -> Long) { + val c = LockFreeLongCounter() + check(c.g() == 0L) + check(c.increment() == 1L) + check(c.g() == 1L) + check(c.increment() == 2L) + check(c.g() == 2L) + } + + fun testBasic() = testWith { get() } + + fun testGetInner() = testWith { getInner() } + + fun testAdd2() { + val c = LockFreeLongCounter() + c.add2() + check(c.get() == 2L) + c.add2() + check(c.get() == 4L) + } + + fun testSetM2() { + val c = LockFreeLongCounter() + c.setM2() + check(c.get() == -2L) + } +} + +class LockFreeLongCounter { + private val counter = atomic(0L) + + fun get(): Long = counter.value + + fun increment(): Long = counter.incrementAndGet() + + fun add2() = counter.getAndAdd(2) + + fun setM2() { + counter.value = -2L // LDC instruction here + } + + fun getInner(): Long = Inner().getFromOuter() + + // testing how an inner class can get access to it + private inner class Inner { + fun getFromOuter(): Long = counter.value + } +} + +fun box(): String { + val testClass = LockFreeLongCounterTest() + testClass.testBasic() + testClass.testAdd2() + testClass.testSetM2() + testClass.testGetInner() + return "OK" +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/testData/basic/LockFreeQueueTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/basic/LockFreeQueueTest.kt new file mode 100644 index 0000000..ae36b81 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/basic/LockFreeQueueTest.kt
@@ -0,0 +1,55 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +class LockFreeQueueTest { + fun testBasic() { + val q = LockFreeQueue() + check(q.dequeue() == -1) + q.enqueue(42) + check(q.dequeue() == 42) + check(q.dequeue() == -1) + q.enqueue(1) + q.enqueue(2) + check(q.dequeue() == 1) + check(q.dequeue() == 2) + check(q.dequeue() == -1) + } +} + +// MS-queue +public class LockFreeQueue { + private val head = atomic(Node(0)) + private val tail = atomic(head.value) + + private class Node(val value: Int) { + val next = atomic<Node?>(null) + } + + public fun enqueue(value: Int) { + val node = Node(value) + tail.loop { curTail -> + val curNext = curTail.next.value + if (curNext != null) { + tail.compareAndSet(curTail, curNext) + return@loop + } + if (curTail.next.compareAndSet(null, node)) { + tail.compareAndSet(curTail, node) + return + } + } + } + + public fun dequeue(): Int { + head.loop { curHead -> + val next = curHead.next.value ?: return -1 + if (head.compareAndSet(curHead, next)) return next.value + } + } +} + +fun box(): String { + val testClass = LockFreeQueueTest() + testClass.testBasic() + return "OK" +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/testData/basic/LockFreeStackTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/basic/LockFreeStackTest.kt new file mode 100644 index 0000000..f4c674a --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/basic/LockFreeStackTest.kt
@@ -0,0 +1,71 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +class LockFreeStackTest { + + fun testClear() { + val s = LockFreeStack<String>() + check(s.isEmpty()) + s.pushLoop("A") + check(!s.isEmpty()) + s.clear() + check(s.isEmpty()) + } + + fun testPushPopLoop() { + val s = LockFreeStack<String>() + check(s.isEmpty()) + s.pushLoop("A") + check(!s.isEmpty()) + check(s.popLoop() == "A") + check(s.isEmpty()) + } + + fun testPushPopUpdate() { + val s = LockFreeStack<String>() + check(s.isEmpty()) + s.pushUpdate("A") + check(!s.isEmpty()) + check(s.popUpdate() == "A") + check(s.isEmpty()) + } +} + +class LockFreeStack<T> { + private val top = atomic<Node<T>?>(null) + + private class Node<T>(val value: T, val next: Node<T>?) + + fun isEmpty() = top.value == null + + fun clear() { top.value = null } + + fun pushLoop(value: T) { + top.loop { cur -> + val upd = Node(value, cur) + if (top.compareAndSet(cur, upd)) return + } + } + + fun popLoop(): T? { + top.loop { cur -> + if (cur == null) return null + if (top.compareAndSet(cur, cur.next)) return cur.value + } + } + + fun pushUpdate(value: T) { + top.update { cur -> Node(value, cur) } + } + + fun popUpdate(): T? = + top.getAndUpdate { cur -> cur?.next } ?.value +} + +fun box(): String { + val testClass = LockFreeStackTest() + testClass.testClear() + testClass.testPushPopLoop() + testClass.testPushPopUpdate() + return "OK" +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/testData/basic/LockTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/basic/LockTest.kt new file mode 100644 index 0000000..28afe70 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/basic/LockTest.kt
@@ -0,0 +1,29 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +class LockTest { + private val inProgressLock = atomic(false) + + fun testLock() { + var result = "" + if (inProgressLock.tryAcquire()) { + result = "OK" + } + assertEquals("OK", result) + } +} + +// This function will be removed by transformer +@Suppress("NOTHING_TO_INLINE") +private inline fun AtomicBoolean.tryAcquire(): Boolean = compareAndSet(false, true) + +// This function is here to test if the Kotlin metadata still consistent after transform +// It is used in ReflectionTest, DO NOT REMOVE +@Suppress("UNUSED_PARAMETER") +fun <AA, BB : Number> String.reflectionTest(mapParam: Map<in AA, BB>): List<BB> = error("no impl") + +fun box(): String { + val testClass = LockTest() + testClass.testLock() + return "OK" +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/testData/basic/LoopTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/basic/LoopTest.kt new file mode 100644 index 0000000..f227d56 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/basic/LoopTest.kt
@@ -0,0 +1,97 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +class LoopTest { + private val a = atomic(0) + private val r = atomic<A>(A("aaaa")) + private val rs = atomic<String>("bbbb") + + private class A(val s: String) + + private inline fun casLoop(to: Int): Int { + a.loop { cur -> + if (a.compareAndSet(cur, to)) return a.value + return 777 + } + } + + private inline fun AtomicInt.extensionLoop(to: Int): Int { + loop { cur -> + if (compareAndSet(cur, to)) return value + return 777 + } + } + + private inline fun AtomicInt.extensionLoopMixedReceivers(to: Int): Int { + loop { cur -> + compareAndSet(cur, to) + a.compareAndSet(cur, to) + return value + } + } + + private inline fun AtomicInt.extensionLoopRecursive(to: Int): Int { + loop { cur -> + compareAndSet(cur, to) + a.extensionLoop(5) + return value + } + } + + private inline fun AtomicInt.returnExtensionLoop(to: Int): Int = + loop { cur -> + lazySet(cur + 10) + return if (compareAndSet(cur, to)) value else incrementAndGet() + } + + private inline fun AtomicRef<A>.casLoop(to: String): String = loop { cur -> + if (compareAndSet(cur, A(to))) { + val res = value.s + return "${res}_AtomicRef<A>" + } + } + + private inline fun AtomicRef<String>.casLoop(to: String): String = loop { cur -> + if (compareAndSet(cur, to)) return "${value}_AtomicRef<String>" + } + + fun testDeclarationWithEqualNames() { + check(r.casLoop("kk") == "kk_AtomicRef<A>") + check(rs.casLoop("pp") == "pp_AtomicRef<String>") + } + + fun testIntExtensionLoops() { + check(casLoop(5) == 5) + assertEquals(a.extensionLoop(66), 66) + check(a.returnExtensionLoop(777) == 77) + } + + abstract class Segment<S : Segment<S>>(val id: Int) + class SemaphoreSegment(id: Int) : Segment<SemaphoreSegment>(id) + + private inline fun <S : Segment<S>> AtomicRef<S>.foo( + id: Int, + startFrom: S + ) { + startFrom.getSegmentId() + } + + private inline fun <S : Segment<S>> S.getSegmentId(): Int { + var cur: S = this + return cur.id + } + + fun testInlineFunWithTypeParameter() { + val s = SemaphoreSegment(0) + val sref = atomic(s) + sref.foo(0, s) + } +} + +fun box(): String { + val testClass = LoopTest() + testClass.testIntExtensionLoops() + testClass.testDeclarationWithEqualNames() + testClass.testInlineFunWithTypeParameter() + return "OK" +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/testData/basic/MultiInitTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/basic/MultiInitTest.kt new file mode 100644 index 0000000..724f4b4 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/basic/MultiInitTest.kt
@@ -0,0 +1,30 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +class MultiInitTest { + fun testBasic() { + val t = MultiInit() + check(t.incA() == 1) + check(t.incA() == 2) + check(t.incB() == 1) + check(t.incB() == 2) + } +} + +class MultiInit { + private val a = atomic(0) + private val b = atomic(0) + + fun incA() = a.incrementAndGet() + fun incB() = b.incrementAndGet() + + companion object { + fun foo() {} // just to force some clinit in outer file + } +} + +fun box(): String { + val testClass = MultiInitTest() + testClass.testBasic() + return "OK" +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/testData/basic/ParameterizedInlineFunExtensionTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/basic/ParameterizedInlineFunExtensionTest.kt new file mode 100644 index 0000000..b16cf02 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/basic/ParameterizedInlineFunExtensionTest.kt
@@ -0,0 +1,20 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +class ParameterizedInlineFunExtensionTest { + + internal inline fun <S> AtomicRef<S>.foo(res1: S, res2: S, foo: (S) -> S): S { return res2 } + + private val tail = atomic("aaa") + + fun testClose() { + val res = tail.foo("bbb", "ccc") { s -> s } + assertEquals("ccc", res) + } +} + +fun box(): String { + val testClass = ParameterizedInlineFunExtensionTest() + testClass.testClose() + return "OK" +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/testData/basic/PropertyDeclarationTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/basic/PropertyDeclarationTest.kt new file mode 100644 index 0000000..3a240a4 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/basic/PropertyDeclarationTest.kt
@@ -0,0 +1,34 @@ +import kotlinx.atomicfu.* +import kotlinx.atomicfu.locks.* +import kotlin.test.* + +class PropertyDeclarationTest { + private val a: AtomicInt + private val head: AtomicRef<String> + private val lateIntArr: AtomicIntArray + private val lateRefArr: AtomicArray<String?> + private val lock: ReentrantLock + + init { + a = atomic(0) + head = atomic("AAA") + lateIntArr = AtomicIntArray(55) + lateRefArr = atomicArrayOfNulls<String?>(10) + lock = reentrantLock() + } + + fun test() { + assertEquals(0, a.value) + check(head.compareAndSet("AAA", "BBB")) + assertEquals("BBB", head.value) + assertEquals(0, lateIntArr[35].value) + assertEquals(null, lateRefArr[5].value) + assertEquals(null, lock) + } +} + +fun box(): String { + val testClass = PropertyDeclarationTest() + testClass.test() + return "OK" +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/testData/basic/ScopeTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/basic/ScopeTest.kt new file mode 100644 index 0000000..3ffe028 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/basic/ScopeTest.kt
@@ -0,0 +1,45 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +class AA(val value: Int) { + val b = B(value + 1) + val c = C(D(E(value + 1))) + + fun updateToB(affected: Any): Boolean { + (affected as AtomicState).state.compareAndSet(this, b) + return (affected.state.value is B && (affected.state.value as B).value == value + 1) + } + + fun manyProperties(affected: Any): Boolean { + (affected as AtomicState).state.compareAndSet(this, c.d.e) + return (affected.state.value is E && (affected.state.value as E).x == value + 1) + } +} + +class B (val value: Int) + +class C (val d: D) +class D (val e: E) +class E (val x: Int) + + +class AtomicState(value: Any) { + val state = atomic<Any?>(value) +} + +class ScopeTest { + fun scopeTest() { + val a = AA(0) + val affected: Any = AtomicState(a) + check(a.updateToB(affected)) + val a1 = AA(0) + val affected1: Any = AtomicState(a1) + check(a1.manyProperties(affected1)) + } +} + +fun box(): String { + val testClass = ScopeTest() + testClass.scopeTest() + return "OK" +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/testData/basic/SimpleLockTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/basic/SimpleLockTest.kt new file mode 100644 index 0000000..7f8d0ca --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/basic/SimpleLockTest.kt
@@ -0,0 +1,36 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +class SimpleLockTest { + fun withLock() { + val lock = SimpleLock() + val result = lock.withLock { + "OK" + } + assertEquals("OK", result) + } +} + +class SimpleLock { + private val _locked = atomic(0) + + fun <T> withLock(block: () -> T): T { + // this contrieves construct triggers Kotlin compiler to reuse local variable slot #2 for + // the exception in `finally` clause + try { + _locked.loop { locked -> + check(locked == 0) + if (!_locked.compareAndSet(0, 1)) return@loop // continue + return block() + } + } finally { + _locked.value = 0 + } + } +} + +fun box(): String { + val testClass = SimpleLockTest() + testClass.withLock() + return "OK" +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/testData/basic/TopLevelTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/basic/TopLevelTest.kt new file mode 100644 index 0000000..22fe616 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/basic/TopLevelTest.kt
@@ -0,0 +1,178 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +private val a = atomic(0) +private val b = atomic(2424920024888888848) +private val c = atomic(true) +private val abcNode = atomic(ANode(BNode(CNode(8)))) +private val any = atomic<Any?>(null) + +private val intArr = AtomicIntArray(3) +private val longArr = AtomicLongArray(5) +private val booleanArr = AtomicBooleanArray(4) +private val refArr = atomicArrayOfNulls<ANode<BNode<CNode>>>(5) +private val anyRefArr = atomicArrayOfNulls<Any>(10) + +private val stringAtomicNullArr = atomicArrayOfNulls<String>(10) + +class TopLevelPrimitiveTest { + + fun testTopLevelInt() { + a.value + check(a.value == 0) + check(a.getAndSet(3) == 0) + check(a.compareAndSet(3, 8)) + a.lazySet(1) + check(a.value == 1) + check(a.getAndSet(2) == 1) + check(a.value == 2) + check(a.getAndIncrement() == 2) + check(a.value == 3) + check(a.getAndDecrement() == 3) + check(a.value == 2) + check(a.getAndAdd(2) == 2) + check(a.value == 4) + check(a.addAndGet(3) == 7) + check(a.value == 7) + check(a.incrementAndGet() == 8) + check(a.value == 8) + check(a.decrementAndGet() == 7) + check(a.value == 7) + a.compareAndSet(7, 10) + } + + fun testTopLevelLong() { + check(b.value == 2424920024888888848) + b.lazySet(8424920024888888848) + check(b.value == 8424920024888888848) + check(b.getAndSet(8924920024888888848) == 8424920024888888848) + check(b.value == 8924920024888888848) + check(b.incrementAndGet() == 8924920024888888849) + check(b.value == 8924920024888888849) + check(b.getAndDecrement() == 8924920024888888849) + check(b.value == 8924920024888888848) + check(b.getAndAdd(100000000000000000) == 8924920024888888848) + check(b.value == 9024920024888888848) + check(b.addAndGet(-9223372036854775807) == -198452011965886959) + check(b.value == -198452011965886959) + check(b.incrementAndGet() == -198452011965886958) + check(b.value == -198452011965886958) + check(b.decrementAndGet() == -198452011965886959) + check(b.value == -198452011965886959) + } + + fun testTopLevelBoolean() { + check(c.value) + c.lazySet(false) + check(!c.value) + check(!c.getAndSet(true)) + check(c.compareAndSet(true, false)) + check(!c.value) + } + + fun testTopLevelRef() { + check(abcNode.value.b.c.d == 8) + val newNode = ANode(BNode(CNode(76))) + check(abcNode.getAndSet(newNode).b.c.d == 8) + check(abcNode.value.b.c.d == 76) + val l = IntArray(4){i -> i} + any.lazySet(l) + check((any.value as IntArray)[2] == 2) + } + + fun testTopLevelArrayOfNulls() { + check(stringAtomicNullArr[0].value == null) + check(stringAtomicNullArr[0].compareAndSet(null, "aa")) + stringAtomicNullArr[1].lazySet("aa") + check(stringAtomicNullArr[0].value == stringAtomicNullArr[1].value) + } +} + +class TopLevelArrayTest { + + fun testIntArray() { + check(intArr[0].compareAndSet(0, 3)) + check(intArr[1].value == 0) + intArr[0].lazySet(5) + check(intArr[0].value + intArr[1].value + intArr[2].value == 5) + check(intArr[0].compareAndSet(5, 10)) + check(intArr[0].getAndDecrement() == 10) + check(intArr[0].value == 9) + intArr[2].value = 2 + check(intArr[2].value == 2) + check(intArr[2].compareAndSet(2, 34)) + check(intArr[2].value == 34) + } + + fun testLongArray() { + longArr[0].value = 2424920024888888848 + check(longArr[0].value == 2424920024888888848) + longArr[0].lazySet(8424920024888888848) + check(longArr[0].value == 8424920024888888848) + val ac = longArr[0].value + longArr[3].value = ac + check(longArr[3].getAndSet(8924920024888888848) == 8424920024888888848) + check(longArr[3].value == 8924920024888888848) + val ac1 = longArr[3].value + longArr[4].value = ac1 + check(longArr[4].incrementAndGet() == 8924920024888888849) + check(longArr[4].value == 8924920024888888849) + check(longArr[4].getAndDecrement() == 8924920024888888849) + check(longArr[4].value == 8924920024888888848) + longArr[4].value = 8924920024888888848 + check(longArr[4].getAndAdd(100000000000000000) == 8924920024888888848) + val ac2 = longArr[4].value + longArr[1].value = ac2 + check(longArr[1].value == 9024920024888888848) + check(longArr[1].addAndGet(-9223372036854775807) == -198452011965886959) + check(longArr[1].value == -198452011965886959) + check(longArr[1].incrementAndGet() == -198452011965886958) + check(longArr[1].value == -198452011965886958) + check(longArr[1].decrementAndGet() == -198452011965886959) + check(longArr[1].value == -198452011965886959) + } + + fun testBooleanArray() { + check(!booleanArr[1].value) + booleanArr[1].compareAndSet(false, true) + booleanArr[0].lazySet(true) + check(!booleanArr[2].getAndSet(true)) + check(booleanArr[0].value && booleanArr[1].value && booleanArr[2].value) + } + + @Suppress("UNCHECKED_CAST") + fun testRefArray() { + val a2 = ANode(BNode(CNode(2))) + val a3 = ANode(BNode(CNode(3))) + refArr[0].value = a2 + check(refArr[0].value!!.b.c.d == 2) + check(refArr[0].compareAndSet(a2, a3)) + check(refArr[0].value!!.b.c.d == 3) + val r0 = refArr[0].value + refArr[3].value = r0 + check(refArr[3].value!!.b.c.d == 3) + val a = abcNode.value + check(refArr[3].compareAndSet(a3, a)) + } + +} + +data class ANode<T>(val b: T) +data class BNode<T>(val c: T) +data class CNode(val d: Int) + +fun box(): String { + val primitiveTest = TopLevelPrimitiveTest() + primitiveTest.testTopLevelInt() + primitiveTest.testTopLevelLong() + primitiveTest.testTopLevelBoolean() + primitiveTest.testTopLevelRef() + primitiveTest.testTopLevelArrayOfNulls() + + val arrayTest = TopLevelArrayTest() + arrayTest.testIntArray() + arrayTest.testLongArray() + arrayTest.testBooleanArray() + arrayTest.testRefArray() + return "OK" +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/testData/basic/UncheckedCastTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/basic/UncheckedCastTest.kt new file mode 100644 index 0000000..d3fd78d --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/basic/UncheckedCastTest.kt
@@ -0,0 +1,54 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +private val topLevelS = atomic<Any>(arrayOf("A", "B")) + +class UncheckedCastTest { + private val s = atomic<Any>("AAA") + private val bs = atomic<Any?>(null) + + @Suppress("UNCHECKED_CAST") + fun testAtomicValUncheckedCast() { + assertEquals((s as AtomicRef<String>).value, "AAA") + bs.lazySet(arrayOf(arrayOf(Box(1), Box(2)))) + assertEquals((bs as AtomicRef<Array<Array<Box>>>).value[0]!![0].b * 10, 10) + } + + @Suppress("UNCHECKED_CAST") + fun testTopLevelValUnchekedCast() { + assertEquals((topLevelS as AtomicRef<Array<String>>).value[1], "B") + } + + private data class Box(val b: Int) + + @Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST") + private inline fun <T> AtomicRef<T>.getString(): String = + (this as AtomicRef<String>).value + + fun testInlineFunc() { + assertEquals("AAA", s.getString()) + } + + private val a = atomicArrayOfNulls<Any?>(10) + + fun testArrayValueUncheckedCast() { + a[0].value = "OK" + @Suppress("UNCHECKED_CAST") + assertEquals("OK", (a[0] as AtomicRef<String>).value) + } + + fun testArrayValueUncheckedCastInlineFunc() { + a[0].value = "OK" + assertEquals("OK", a[0].getString()) + } +} + +fun box(): String { + val testClass = UncheckedCastTest() + testClass.testTopLevelValUnchekedCast() + testClass.testArrayValueUncheckedCast() + testClass.testArrayValueUncheckedCastInlineFunc() + testClass.testAtomicValUncheckedCast() + testClass.testInlineFunc() + return "OK" +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/testData/locks/ReentrantLockTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/locks/ReentrantLockTest.kt new file mode 100644 index 0000000..e04d7ad --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/locks/ReentrantLockTest.kt
@@ -0,0 +1,20 @@ +import kotlinx.atomicfu.locks.* +import kotlin.test.* + +class ReentrantLockTest { + private val lock = reentrantLock() + private var state = 0 + + fun testLockField() { + lock.withLock { + state = 1 + } + assertEquals(1, state) + } +} + +fun box(): String { + val testClass = ReentrantLockTest() + testClass.testLockField() + return "OK" +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-compiler/testData/locks/SynchronizedObjectTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/locks/SynchronizedObjectTest.kt new file mode 100644 index 0000000..98efc96 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/locks/SynchronizedObjectTest.kt
@@ -0,0 +1,21 @@ +import kotlinx.atomicfu.locks.* +import kotlin.test.* + +class SynchronizedObjectTest : SynchronizedObject() { + + fun testSync() { + val result = synchronized(this) { bar() } + assertEquals("OK", result) + } + + private fun bar(): String = + synchronized(this) { + "OK" + } +} + +fun box(): String { + val testClass = SynchronizedObjectTest() + testClass.testSync() + return "OK" +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-runtime/build.gradle.kts b/plugins/atomicfu/atomicfu-runtime/build.gradle.kts new file mode 100644 index 0000000..65b7ca0 --- /dev/null +++ b/plugins/atomicfu/atomicfu-runtime/build.gradle.kts
@@ -0,0 +1,33 @@ +description = "Atomicfu Runtime" + +plugins { + kotlin("js") + `maven-publish` +} + +group = "org.jetbrains.kotlin" + +repositories { + mavenLocal() + jcenter() +} + +kotlin { + js() + + sourceSets { + js().compilations["main"].defaultSourceSet { + dependencies { + compileOnly(kotlin("stdlib-js")) + } + } + } +} + +publishing { + publications { + create<MavenPublication>("maven") { + from(components["kotlin"]) + } + } +} \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-runtime/gradle.properties b/plugins/atomicfu/atomicfu-runtime/gradle.properties new file mode 100644 index 0000000..860acd1 --- /dev/null +++ b/plugins/atomicfu/atomicfu-runtime/gradle.properties
@@ -0,0 +1 @@ +kotlin.js.compiler=both \ No newline at end of file
diff --git a/plugins/atomicfu/atomicfu-runtime/src/main/kotlin/atomicfu.kt b/plugins/atomicfu/atomicfu-runtime/src/main/kotlin/atomicfu.kt new file mode 100644 index 0000000..1e776d3 --- /dev/null +++ b/plugins/atomicfu/atomicfu-runtime/src/main/kotlin/atomicfu.kt
@@ -0,0 +1,130 @@ +/* + * Copyright 2010-2020 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 kotlinx.atomicfu + +internal inline fun <T> atomicfu_getValue(`atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit): T { + return `atomicfu$getter`() +} + +internal inline fun <T> atomicfu_setValue(value: T, `atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit): Unit { + `atomicfu$setter`(value) +} + +internal inline fun <T> atomicfu_lazySet(value: T, `atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit): Unit { + `atomicfu$setter`(value) +} + +internal inline fun <T> atomicfu_compareAndSet(expect: T, update: T, `atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit): Boolean { + if (`atomicfu$getter`() == expect) { + `atomicfu$setter`(update) + return true + } else { + return false + } +} + +internal inline fun <T> atomicfu_getAndSet(value: T, `atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit): T { + val oldValue = `atomicfu$getter`() + `atomicfu$setter`(value) + return oldValue +} + +internal inline fun atomicfu_getAndIncrement(`atomicfu$getter`: () -> Int, `atomicfu$setter`: (Int) -> Unit): Int { + val oldValue = `atomicfu$getter`() + `atomicfu$setter`(oldValue + 1) + return oldValue +} + +internal inline fun atomicfu_getAndIncrement(`atomicfu$getter`: () -> Long, `atomicfu$setter`: (Long) -> Unit): Long { + val oldValue = `atomicfu$getter`() + `atomicfu$setter`(oldValue + 1) + return oldValue +} + +internal inline fun atomicfu_incrementAndGet(`atomicfu$getter`: () -> Int, `atomicfu$setter`: (Int) -> Unit): Int { + `atomicfu$setter`(`atomicfu$getter`() + 1) + return `atomicfu$getter`() +} + +internal inline fun atomicfu_incrementAndGet(`atomicfu$getter`: () -> Long, `atomicfu$setter`: (Long) -> Unit): Long { + `atomicfu$setter`(`atomicfu$getter`() + 1) + return `atomicfu$getter`() +} + +internal inline fun atomicfu_getAndDecrement(`atomicfu$getter`: () -> Int, `atomicfu$setter`: (Int) -> Unit): Int { + val oldValue = `atomicfu$getter`() + `atomicfu$setter`(oldValue - 1) + return oldValue +} + +internal inline fun atomicfu_getAndDecrement(`atomicfu$getter`: () -> Long, `atomicfu$setter`: (Long) -> Unit): Long { + val oldValue = `atomicfu$getter`() + `atomicfu$setter`(oldValue - 1) + return oldValue +} + +internal inline fun atomicfu_decrementAndGet(`atomicfu$getter`: () -> Int, `atomicfu$setter`: (Int) -> Unit): Int { + `atomicfu$setter`(`atomicfu$getter`() - 1) + return `atomicfu$getter`() +} + +internal inline fun atomicfu_decrementAndGet(`atomicfu$getter`: () -> Long, `atomicfu$setter`: (Long) -> Unit): Long { + `atomicfu$setter`(`atomicfu$getter`() - 1) + return `atomicfu$getter`() +} + +internal inline fun atomicfu_getAndAdd(value: Int, `atomicfu$getter`: () -> Int, `atomicfu$setter`: (Int) -> Unit): Int { + val oldValue = `atomicfu$getter`() + `atomicfu$setter`(oldValue + value) + return oldValue +} + +internal inline fun atomicfu_getAndAdd(value: Long, `atomicfu$getter`: () -> Long, `atomicfu$setter`: (Long) -> Unit): Long { + val oldValue = `atomicfu$getter`() + `atomicfu$setter`(oldValue + value) + return oldValue +} + +internal inline fun atomicfu_addAndGet(value: Int, `atomicfu$getter`: () -> Int, `atomicfu$setter`: (Int) -> Unit): Int { + `atomicfu$setter`(`atomicfu$getter`() + value) + return `atomicfu$getter`() +} + +internal inline fun atomicfu_addAndGet(value: Long, `atomicfu$getter`: () -> Long, `atomicfu$setter`: (Long) -> Unit): Long { + `atomicfu$setter`(`atomicfu$getter`() + value) + return `atomicfu$getter`() +} + +internal inline fun <T> atomicfu_loop(action: (T) -> Unit, `atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit): Nothing { + val cur = `atomicfu$getter`() + while (true) { + action(cur) + } +} + +internal inline fun <T> atomicfu_update(function: (T) -> T, `atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit) { + while (true) { + val cur = `atomicfu$getter`() + val upd = function(cur) + if (atomicfu_compareAndSet(cur, upd, `atomicfu$getter`, `atomicfu$setter`)) return + } +} + +internal inline fun <T> atomicfu_getAndUpdate(function: (T) -> T, `atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit): T { + while (true) { + val cur = `atomicfu$getter`() + val upd = function(cur) + if (atomicfu_compareAndSet(cur, upd, `atomicfu$getter`, `atomicfu$setter`)) return cur + } +} + +internal inline fun <T> atomicfu_updateAndGet(function: (T) -> T, `atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit): T { + while (true) { + val cur = `atomicfu$getter`() + val upd = function(cur) + if (atomicfu_compareAndSet(cur, upd, `atomicfu$getter`, `atomicfu$setter`)) return upd + } +} \ No newline at end of file
diff --git a/settings.gradle b/settings.gradle index 619cf70..8b40365 100644 --- a/settings.gradle +++ b/settings.gradle
@@ -308,6 +308,9 @@ ":kotlin-serialization-unshaded", ":wasm:wasm.ir" +include ":kotlinx-atomicfu-compiler-plugin", + ":kotlinx-atomicfu-runtime", + ":atomicfu" include ":compiler:fir:cones", ":compiler:fir:tree", @@ -581,6 +584,10 @@ project(':kotlin-serialization').projectDir = file("$rootDir/libraries/tools/kotlin-serialization") project(':kotlin-serialization-unshaded').projectDir = file("$rootDir/libraries/tools/kotlin-serialization-unshaded") +project(':kotlinx-atomicfu-compiler-plugin').projectDir = file("$rootDir/plugins/atomicfu/atomicfu-compiler") +project(':kotlinx-atomicfu-runtime').projectDir = file("$rootDir/plugins/atomicfu/atomicfu-runtime") +project(':atomicfu').projectDir = file("$rootDir/libraries/tools/atomicfu") + // Uncomment to use locally built protobuf-relocated // includeBuild("dependencies/protobuf") if (buildProperties.isKotlinNativeEnabled) {