From 93561a1a55f7f1540577d1c835565222a4ba4019 Mon Sep 17 00:00:00 2001 From: mvicsokolova <82594708+mvicsokolova@users.noreply.github.com> Date: Wed, 1 Dec 2021 22:33:13 +0300 Subject: [PATCH] kotlinx.atomicfu compiler plugin for JS_IR backend (#4581) * kotlinx.atomicfu compiler plugin for JS_IR Support transformations of atomic operations introduced by the kotlinx.atomicfu library for the JS_IR backend. Compiler plugin is applied externally by the kotlinx.atomicfu gradle plugin. * Apply compiler plugin for JS platform only * New plugin test structure * testGroupOutputDirPrefix changed --- .../frontend/classic/ClassicFrontendFacade.kt | 2 +- .../JsEnvironmentConfigurator.kt | 8 +- generators/build.gradle.kts | 2 + .../kotlin/generators/tests/GenerateTests.kt | 11 + gradle/verification-metadata.xml | 5 + .../test/utils/JsIrIncrementalDataProvider.kt | 2 +- libraries/configureGradleTools.gradle | 1 + libraries/tools/atomicfu/build.gradle | 40 ++ .../gradle/AtomicfuKotlinGradleSubplugin.kt | 58 +++ .../kotlinx-atomicfu.properties | 1 + ...etbrains.kotlin.plugin.atomicfu.properties | 1 + ...kotlin.gradle.plugin.KotlinGradleSubplugin | 17 + .../atomicfu-compiler/build.gradle.kts | 132 ++++++ ....kotlin.compiler.plugin.ComponentRegistrar | 17 + .../extensions/AtomicfuComponentRegistrar.kt | 34 ++ .../extensions/AtomicfuLoweringExtension.kt | 53 +++ .../extensions/AtomicfuTransformer.kt | 382 ++++++++++++++++++ .../extensions/TransformerUtil.kt | 265 ++++++++++++ .../atomicfu/AbstractAtomicfuJsIrTest.kt | 45 +++ .../atomicfu/AtomicfuJsIrTestGenerated.java | 153 +++++++ .../testData/box/ArithmeticTest.kt | 131 ++++++ .../testData/box/ArrayInlineFunctionTest.kt | 47 +++ .../testData/box/AtomicArrayTest.kt | 97 +++++ .../testData/box/ExtensionsTest.kt | 115 ++++++ .../box/IndexArrayElementGetterTest.kt | 32 ++ .../InlineExtensionWithTypeParameterTest.kt | 31 ++ .../testData/box/LockFreeIntBitsTest.kt | 57 +++ .../testData/box/LockFreeLongCounterTest.kt | 61 +++ .../testData/box/LockFreeQueueTest.kt | 55 +++ .../testData/box/LockFreeStackTest.kt | 71 ++++ .../testData/box/LockTest.kt | 29 ++ .../testData/box/LoopTest.kt | 82 ++++ .../testData/box/MultiInitTest.kt | 30 ++ .../ParameterizedInlineFunExtensionTest.kt | 27 ++ .../testData/box/PropertyDeclarationTest.kt | 34 ++ .../testData/box/ReentrantLockTest.kt | 20 + .../testData/box/ScopeTest.kt | 45 +++ .../testData/box/SimpleLockTest.kt | 36 ++ .../testData/box/SynchronizedObjectTest.kt | 21 + .../testData/box/TopLevelTest.kt | 178 ++++++++ .../testData/box/UncheckedCastTest.kt | 54 +++ .../atomicfu-runtime/build.gradle.kts | 23 ++ .../atomicfu-runtime/gradle.properties | 1 + .../src/main/kotlin/atomicfu.kt | 146 +++++++ settings.gradle | 7 + 45 files changed, 2655 insertions(+), 4 deletions(-) create mode 100644 libraries/tools/atomicfu/build.gradle create mode 100644 libraries/tools/atomicfu/src/main/kotlin/org/jetbrains/kotlinx/atomicfu/gradle/AtomicfuKotlinGradleSubplugin.kt create mode 100644 libraries/tools/atomicfu/src/main/resources/META-INF/gradle-plugins/kotlinx-atomicfu.properties create mode 100644 libraries/tools/atomicfu/src/main/resources/META-INF/gradle-plugins/org.jetbrains.kotlin.plugin.atomicfu.properties create mode 100644 libraries/tools/atomicfu/src/main/resources/META-INF/services/org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin create mode 100644 plugins/atomicfu/atomicfu-compiler/build.gradle.kts create mode 100644 plugins/atomicfu/atomicfu-compiler/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar create mode 100644 plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/AtomicfuComponentRegistrar.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/AtomicfuLoweringExtension.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/AtomicfuTransformer.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/TransformerUtil.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/test/org/jetbrains/kotlinx/atomicfu/AbstractAtomicfuJsIrTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/test/org/jetbrains/kotlinx/atomicfu/AtomicfuJsIrTestGenerated.java create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/ArithmeticTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/ArrayInlineFunctionTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/AtomicArrayTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/ExtensionsTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/IndexArrayElementGetterTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/InlineExtensionWithTypeParameterTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/LockFreeIntBitsTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/LockFreeLongCounterTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/LockFreeQueueTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/LockFreeStackTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/LockTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/LoopTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/MultiInitTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/ParameterizedInlineFunExtensionTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/PropertyDeclarationTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/ReentrantLockTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/ScopeTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/SimpleLockTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/SynchronizedObjectTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/TopLevelTest.kt create mode 100644 plugins/atomicfu/atomicfu-compiler/testData/box/UncheckedCastTest.kt create mode 100644 plugins/atomicfu/atomicfu-runtime/build.gradle.kts create mode 100644 plugins/atomicfu/atomicfu-runtime/gradle.properties create mode 100644 plugins/atomicfu/atomicfu-runtime/src/main/kotlin/atomicfu.kt diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ClassicFrontendFacade.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ClassicFrontendFacade.kt index d317d19884e..362291c88c2 100644 --- a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ClassicFrontendFacade.kt +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ClassicFrontendFacade.kt @@ -314,7 +314,7 @@ class ClassicFrontendFacade( dependentDescriptors: List, friendsDescriptors: List, ): AnalysisResult { - val runtimeKlibsNames = JsEnvironmentConfigurator.getStdlibPathsForModule(module) + val runtimeKlibsNames = JsEnvironmentConfigurator.getRuntimePathsForModule(module, testServices) val runtimeKlibs = loadKlib(runtimeKlibsNames, configuration) val transitiveLibraries = JsEnvironmentConfigurator.getDependencies(module, testServices, DependencyRelation.RegularDependency) val friendLibraries = JsEnvironmentConfigurator.getDependencies(module, testServices, DependencyRelation.FriendDependency) diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/JsEnvironmentConfigurator.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/JsEnvironmentConfigurator.kt index 6c7dfd966f1..f042cf48385 100644 --- a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/JsEnvironmentConfigurator.kt +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/JsEnvironmentConfigurator.kt @@ -137,13 +137,17 @@ class JsEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigu return getMainModule(testServices).name } - fun getStdlibPathsForModule(module: TestModule): List { + fun getRuntimePathsForModule(module: TestModule, testServices: TestServices): List { + val result = mutableListOf() val needsFullIrRuntime = JsEnvironmentConfigurationDirectives.KJS_WITH_FULL_RUNTIME in module.directives || ConfigurationDirectives.WITH_STDLIB in module.directives || ConfigurationDirectives.WITH_RUNTIME in module.directives val names = if (needsFullIrRuntime) listOf("full.stdlib", "kotlin.test") else listOf("reduced.stdlib") - return names.map { System.getProperty("kotlin.js.$it.path") } + names.mapTo(result) { System.getProperty("kotlin.js.$it.path") } + val runtimeClasspaths = testServices.runtimeClasspathProviders.flatMap { it.runtimeClassPaths(module) } + runtimeClasspaths.mapTo(result) { it.absolutePath } + return result } fun getDependencies(module: TestModule, testServices: TestServices, kind: DependencyRelation): List { diff --git a/generators/build.gradle.kts b/generators/build.gradle.kts index b924f652fb9..6f01edf98cd 100644 --- a/generators/build.gradle.kts +++ b/generators/build.gradle.kts @@ -65,6 +65,7 @@ dependencies { testApi(projectTests(":plugins:lombok:lombok-compiler-plugin")) testApi(projectTests(":kotlin-sam-with-receiver-compiler-plugin")) testApi(projectTests(":kotlinx-serialization-compiler-plugin")) + testApi(projectTests(":kotlinx-atomicfu-compiler-plugin")) testApi(projectTests(":plugins:fir:fir-plugin-prototype")) testApi(projectTests(":generators:test-generator")) testCompileOnly(project(":kotlin-reflect-api")) @@ -73,6 +74,7 @@ dependencies { testImplementation(projectTests(":compiler:test-infrastructure-utils")) testImplementation(projectTests(":compiler:test-infrastructure")) testImplementation(projectTests(":compiler:tests-common-new")) + testImplementation(projectTests(":js:js.tests")) testApiJUnit5() if (Ide.IJ()) { diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index c563a45e915..95036338de0 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -38,6 +38,7 @@ import org.jetbrains.kotlin.test.TargetBackend import org.jetbrains.kotlinx.serialization.AbstractSerializationIrBytecodeListingTest import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginBytecodeListingTest import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginDiagnosticTest +import org.jetbrains.kotlinx.atomicfu.AbstractAtomicfuJsIrTest fun main(args: Array) { System.setProperty("java.awt.headless", "true") @@ -395,5 +396,15 @@ fun main(args: Array) { model("box") } } + + testGroup( + "plugins/atomicfu/atomicfu-compiler/test", + "plugins/atomicfu/atomicfu-compiler/testData", + testRunnerMethodName = "runTest0" + ) { + testClass { + model("box/") + } + } } } diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 0485886e0bf..cc4ab82b457 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -7258,6 +7258,11 @@ + + + + + diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/JsIrIncrementalDataProvider.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/JsIrIncrementalDataProvider.kt index e3ab8dd91b8..b74f2b8085c 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/JsIrIncrementalDataProvider.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/JsIrIncrementalDataProvider.kt @@ -162,7 +162,7 @@ class JsIrIncrementalDataProvider(private val testServices: TestServices) : Test } private fun recordIncrementalDataForRuntimeKlib(module: TestModule) { - val runtimeKlibPath = JsEnvironmentConfigurator.getStdlibPathsForModule(module) + val runtimeKlibPath = JsEnvironmentConfigurator.getRuntimePathsForModule(module, testServices) val libs = runtimeKlibPath.map { val descriptor = testServices.jsLibraryProvider.getDescriptorByPath(it) testServices.jsLibraryProvider.getCompiledLibraryByDescriptor(descriptor) diff --git a/libraries/configureGradleTools.gradle b/libraries/configureGradleTools.gradle index 4434b0939ad..5779bc8ea5a 100644 --- a/libraries/configureGradleTools.gradle +++ b/libraries/configureGradleTools.gradle @@ -3,6 +3,7 @@ def pluginProjects = [ project(':kotlin-allopen'), project(':kotlin-noarg'), project(':kotlin-serialization'), + project(':atomicfu'), project(':kotlin-lombok') ] configure(pluginProjects) { project -> diff --git a/libraries/tools/atomicfu/build.gradle b/libraries/tools/atomicfu/build.gradle new file mode 100644 index 00000000000..bb70820e460 --- /dev/null +++ b/libraries/tools/atomicfu/build.gradle @@ -0,0 +1,40 @@ +repositories { + mavenLocal() + 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(':kotlin-compiler-embeddable') + + embedded(project(":kotlinx-atomicfu-compiler-plugin")) +} + +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/AtomicfuKotlinGradleSubplugin.kt b/libraries/tools/atomicfu/src/main/kotlin/org/jetbrains/kotlinx/atomicfu/gradle/AtomicfuKotlinGradleSubplugin.kt new file mode 100644 index 00000000000..8f3413a42ad --- /dev/null +++ b/libraries/tools/atomicfu/src/main/kotlin/org/jetbrains/kotlinx/atomicfu/gradle/AtomicfuKotlinGradleSubplugin.kt @@ -0,0 +1,58 @@ +/* + * 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.Project +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.compile.AbstractCompile +import org.jetbrains.kotlin.gradle.plugin.* + +class AtomicfuKotlinGradleSubplugin : + KotlinCompilerPluginSupportPlugin, + @Suppress("DEPRECATION_ERROR") // implementing to fix KT-39809 + KotlinGradleSubplugin { + companion object { + const val ATOMICFU_ARTIFACT_NAME = "atomicfu" + } + + override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean = true + + override fun applyToCompilation( + kotlinCompilation: KotlinCompilation<*> + ): Provider> = + kotlinCompilation.target.project.provider { emptyList() } + + override fun getPluginArtifact(): SubpluginArtifact = + JetBrainsSubpluginArtifact(ATOMICFU_ARTIFACT_NAME) + + override fun getCompilerPluginId() = "org.jetbrains.kotlinx.atomicfu" + + //region Stub implementation for legacy API, KT-39809 + override fun isApplicable(project: Project, task: AbstractCompile): Boolean = true + + override fun apply( + project: Project, + kotlinCompile: AbstractCompile, + javaCompile: AbstractCompile?, + variantData: Any?, + androidProjectHandler: Any?, + kotlinCompilation: KotlinCompilation<*>? + ): List { + return emptyList() + } + //endregion +} \ No newline at end of file diff --git a/libraries/tools/atomicfu/src/main/resources/META-INF/gradle-plugins/kotlinx-atomicfu.properties b/libraries/tools/atomicfu/src/main/resources/META-INF/gradle-plugins/kotlinx-atomicfu.properties new file mode 100644 index 00000000000..b07d10d4109 --- /dev/null +++ b/libraries/tools/atomicfu/src/main/resources/META-INF/gradle-plugins/kotlinx-atomicfu.properties @@ -0,0 +1 @@ +implementation-class=org.jetbrains.kotlinx.atomicfu.gradle.AtomicfuKotlinGradleSubplugin \ 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 00000000000..b07d10d4109 --- /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.AtomicfuKotlinGradleSubplugin \ 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 00000000000..82e8567f12c --- /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 00000000000..8044a410e19 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/build.gradle.kts @@ -0,0 +1,132 @@ +import org.jetbrains.kotlin.gradle.targets.js.KotlinJsCompilerAttribute +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages + +description = "Atomicfu Compiler Plugin" + +plugins { + kotlin("jvm") + id("jps-compatible") + id("com.github.node-gradle.node") version "2.2.0" + id("de.undercouch.download") +} + +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 { + mavenCentral() +} + +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")) + compileOnly(project(":compiler:backend.js")) + + compileOnly(kotlinStdlib()) + + testApi(projectTests(":compiler:tests-common")) + testApi(projectTests(":compiler:test-infrastructure")) + testApi(projectTests(":compiler:test-infrastructure-utils")) + testApi(projectTests(":compiler:tests-compiler-utils")) + testApi(projectTests(":compiler:tests-common-new")) + testImplementation(projectTests(":generators:test-generator")) + + testImplementation(projectTests(":js:js.tests")) + testApi(commonDep("junit:junit")) + + testRuntimeOnly(kotlinStdlib()) + testRuntimeOnly(project(":kotlin-reflect")) + testRuntimeOnly(project(":kotlin-preloader")) // it's required for ant tests + testRuntimeOnly(project(":compiler:backend-common")) + testRuntimeOnly(commonDep("org.fusesource.jansi", "jansi")) + + atomicfuClasspath("org.jetbrains.kotlinx:atomicfu-js:0.16.3") { isTransitive = false } + atomicfuRuntimeForTests(project(":kotlinx-atomicfu-runtime")) { isTransitive = false } + + embedded(project(":kotlinx-atomicfu-runtime")) { + attributes { + attribute(KotlinPlatformType.attribute, KotlinPlatformType.js) + attribute(KotlinJsCompilerAttribute.jsCompilerAttribute, KotlinJsCompilerAttribute.ir) + attribute(Usage.USAGE_ATTRIBUTE, objects.named(KotlinUsages.KOTLIN_RUNTIME)) + } + isTransitive = false + } + + testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.6.2") +} + +sourceSets { + "main" { projectDefault() } + "test" { projectDefault() } +} + +runtimeJar() +sourcesJar() +javadocJar() +testsJar() + +projectTest(jUnitMode = JUnitMode.JUnit5) { + useJUnitPlatform() + workingDir = rootDir + dependsOn(atomicfuRuntimeForTests) + doFirst { + systemProperty("atomicfuRuntimeForTests.classpath", atomicfuRuntimeForTests.asPath) + systemProperty("atomicfu.classpath", atomicfuClasspath.asPath) + } + setUpJsIrBoxTests() +} + +fun Test.setupV8() { + dependsOn(":js:js.tests:unzipV8") + doFirst { + val unzipV8Task = project.tasks.getByPath(":js:js.tests:unzipV8") + systemProperty("javascript.engine.path.V8", File(unzipV8Task.outputs.files.single().path, "d8")) + } +} + +fun Test.setUpJsIrBoxTests() { + setupV8() + + dependsOn(":dist") + 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/") +} \ 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 00000000000..b3e8636af5f --- /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/AtomicfuComponentRegistrar.kt b/plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/AtomicfuComponentRegistrar.kt new file mode 100644 index 00000000000..b33555fc4e1 --- /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 00000000000..0ce8c33f5fc --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/AtomicfuLoweringExtension.kt @@ -0,0 +1,53 @@ +/* + * 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 + +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(context).transform(irFile) + } +} \ No newline at end of file diff --git a/plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/AtomicfuTransformer.kt b/plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/AtomicfuTransformer.kt new file mode 100644 index 00000000000..389d4a2a353 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/AtomicfuTransformer.kt @@ -0,0 +1,382 @@ +/* + * 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.ir.* +import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder.buildValueParameter +import org.jetbrains.kotlin.ir.util.IdSignature.* +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.types.* +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.expressions.IrTypeOperator.* +import org.jetbrains.kotlin.ir.visitors.IrElementTransformer +import org.jetbrains.kotlin.platform.js.isJs + +private const val AFU_PKG = "kotlinx.atomicfu" +private const val LOCKS = "locks" +private const val AFU_LOCKS_PKG = "$AFU_PKG.$LOCKS" +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 ATOMIC_VALUE_FACTORY = "atomic" +private const val ATOMIC_ARRAY_OF_NULLS_FACTORY = "atomicArrayOfNulls" +private const val REENTRANT_LOCK_FACTORY = "reentrantLock" + +class AtomicfuTransformer(private val context: IrPluginContext) { + + private val irBuiltIns = context.irBuiltIns + + private val AFU_CLASSES: Map = mapOf( + "AtomicInt" to irBuiltIns.intType, + "AtomicLong" to irBuiltIns.longType, + "AtomicRef" to irBuiltIns.anyNType, + "AtomicBoolean" to irBuiltIns.booleanType + ) + + private val ATOMIC_VALUE_TYPES = setOf("AtomicInt", "AtomicLong", "AtomicBoolean", "AtomicRef") + private val ATOMIC_ARRAY_TYPES = setOf("AtomicIntArray", "AtomicLongArray", "AtomicBooleanArray", "AtomicArray") + private val ATOMICFU_INLINE_FUNCTIONS = setOf("atomicfu_loop", "atomicfu_update", "atomicfu_getAndUpdate", "atomicfu_updateAndGet") + + fun transform(irFile: IrFile) { + if (context.platform.isJs()) { + irFile.transform(AtomicExtensionTransformer(), null) + irFile.transformChildren(AtomicTransformer(), null) + + irFile.patchDeclarationParents() + } + } + + private inner class AtomicExtensionTransformer : IrElementTransformerVoid() { + override fun visitFile(declaration: IrFile): IrFile { + declaration.declarations.addAllTransformedAtomicExtensions() + return super.visitFile(declaration) + } + + override fun visitClass(declaration: IrClass): IrStatement { + declaration.declarations.addAllTransformedAtomicExtensions() + return super.visitClass(declaration) + } + + private fun MutableList.addAllTransformedAtomicExtensions() { + val transformedDeclarations = mutableListOf() + forEach { irDeclaration -> + irDeclaration.transformAtomicExtension()?.let { it -> transformedDeclarations.add(it) } + } + addAll(transformedDeclarations) + } + + private fun IrDeclaration.transformAtomicExtension(): IrDeclaration? { + // Transform the signature of the inline Atomic* extension declaration: + // inline fun AtomicRef.foo(arg) { ... } -> inline fun foo(arg', atomicfu$getter: () -> T, atomicfu$setter: (T) -> Unit) + if (this is IrFunction && isAtomicExtension()) { + val newDeclaration = deepCopyWithSymbols(parent) + val valueParametersCount = valueParameters.size + val type = newDeclaration.extensionReceiverParameter!!.type.atomicToValueType() + val getterType = context.buildGetterType(type) + val setterType = context.buildSetterType(type) + newDeclaration.valueParameters = newDeclaration.valueParameters + listOf( + buildValueParameter(newDeclaration, GETTER, valueParametersCount, getterType), + buildValueParameter(newDeclaration, SETTER, valueParametersCount + 1, setterType) + ) + newDeclaration.extensionReceiverParameter = null + return newDeclaration + } + return null + } + } + + private inner class AtomicTransformer : IrElementTransformer { + + override fun visitFunction(declaration: IrFunction, data: IrFunction?): IrStatement { + return super.visitFunction(declaration, declaration) + } + + override fun visitCall(expression: IrCall, data: IrFunction?): IrElement { + expression.eraseAtomicFactory()?.let { return it.transform(this, data) } + val isInline = expression.symbol.owner.isInline + (expression.extensionReceiver ?: expression.dispatchReceiver)?.transform(this, data)?.let { receiver -> + // Transform invocations of atomic functions + if (expression.symbol.isKotlinxAtomicfuPackage() && receiver.type.isAtomicValueType()) { + // Substitute invocations of atomic functions on atomic receivers + // with the corresponding inline declarations from `kotlinx-atomicfu-runtime`, + // passing atomic receiver accessors as atomicfu$getter and atomicfu$setter parameters. + + // In case of the atomic field receiver, pass field accessors: + // a.incrementAndGet() -> atomicfu_incrementAndGet(get_a {..}, set_a {..}) + + // In case of the atomic `this` receiver, pass the corresponding atomicfu$getter and atomicfu$setter parameters + // from the parent transformed atomic extension declaration: + // Note: inline atomic extension signatures are already transformed with the [AtomicExtensionTransformer] + // inline fun foo(atomicfu$getter: () -> T, atomicfu$setter: (T) -> Unit) { incrementAndGet() } -> + // inline fun foo(atomicfu$getter: () -> T, atomicfu$setter: (T) -> Unit) { atomicfu_incrementAndGet(atomicfu$getter, atomicfu$setter) } + receiver.getReceiverAccessors(data)?.let { accessors -> + val receiverValueType = receiver.type.atomicToValueType() + val inlineAtomic = expression.inlineAtomicFunction(receiverValueType, accessors).apply { + if (symbol.owner.name.asString() in ATOMICFU_INLINE_FUNCTIONS) { + val lambdaLoop = (getValueArgument(0) as IrFunctionExpression).function + lambdaLoop.body?.transform(this@AtomicTransformer, data) + } + } + return super.visitCall(inlineAtomic, data) + } + } + // Transform invocations of atomic extension functions + if (isInline && receiver.type.isAtomicValueType()) { + // Transform invocation of the atomic extension on the atomic receiver, + // passing field accessors as atomicfu$getter and atomicfu$setter parameters. + + // In case of the atomic field receiver, pass field accessors: + // a.foo(arg) -> foo(arg, get_a {..}, set_a {..}) + + // In case of the atomic `this` receiver, pass the corresponding atomicfu$getter and atomicfu$setter parameters + // from the parent transformed atomic extension declaration: + // Note: inline atomic extension signatures are already transformed with the [AtomicExtensionTransformer] + // inline fun bar(atomicfu$getter: () -> T, atomicfu$setter: (T) -> Unit) { ... } + // inline fun foo(atomicfu$getter: () -> T, atomicfu$setter: (T) -> Unit) { this.bar() } -> + // inline fun foo(atomicfu$getter: () -> T, atomicfu$setter: (T) -> Unit) { bar(atomicfu$getter, atomicfu$setter) } + receiver.getReceiverAccessors(data)?.let { accessors -> + val declaration = expression.symbol.owner + val transformedAtomicExtension = getDeclarationWithAccessorParameters(declaration, declaration.extensionReceiverParameter) + return buildCall( + expression.startOffset, + expression.endOffset, + target = transformedAtomicExtension.symbol, + type = expression.type, + valueArguments = expression.getValueArguments() + accessors + ).apply { + dispatchReceiver = expression.dispatchReceiver + } + } + } + } + return super.visitCall(expression, data) + } + + override fun visitGetValue(expression: IrGetValue, data: IrFunction?): IrExpression { + // For transformed atomic extension functions: + // replace all usages of old value parameters with the new parameters of the transformed declaration + // inline fun foo(arg', atomicfu$getter: () -> T, atomicfu$setter: (T) -> Unit) { bar(arg) } -> { bar(arg') } + if (expression.symbol is IrValueParameterSymbol) { + val valueParameter = expression.symbol.owner as IrValueParameter + val parent = valueParameter.parent + if (parent is IrFunction && parent.isTransformedAtomicExtensionFunction()) { + val index = valueParameter.index + if (index >= 0) { // index == -1 for `this` parameter + val transformedValueParameter = parent.valueParameters[index] + return buildGetValue( + expression.startOffset, + expression.endOffset, + transformedValueParameter.symbol + ) + } + } + } + return super.visitGetValue(expression, data) + } + + override fun visitTypeOperator(expression: IrTypeOperatorCall, data: IrFunction?): IrExpression { + // Erase unchecked casts: + // val a = atomic("AAA") + // (a as AtomicRef).value -> a.value + if ((expression.operator == CAST || expression.operator == IMPLICIT_CAST) && expression.typeOperand.isAtomicValueType()) { + return expression.argument + } + return super.visitTypeOperator(expression, data) + } + + override fun visitConstructorCall(expression: IrConstructorCall, data: IrFunction?): IrElement { + // Erase constructor of Atomic(Int|Long|Boolean|)Array: + // val arr = AtomicIntArray(size) -> val arr = new Int32Array(size) + if (expression.isAtomicArrayConstructor()) { + val arrayConstructorSymbol = + context.getArrayConstructorSymbol(expression.type as IrSimpleType) { it.owner.valueParameters.size == 1 } + val size = expression.getValueArgument(0) + return IrConstructorCallImpl( + expression.startOffset, expression.endOffset, + arrayConstructorSymbol.owner.returnType, arrayConstructorSymbol, + arrayConstructorSymbol.owner.typeParameters.size, 0, 1 + ).apply { + putValueArgument(0, size) + } + } + return super.visitConstructorCall(expression, data) + } + + private fun IrExpression.getReceiverAccessors(parent: IrFunction?): List? = + when { + this is IrCall -> getAccessors() + isThisReceiver() -> { + if (parent is IrFunction && parent.isTransformedAtomicExtensionFunction()) { + parent.valueParameters.takeLast(2).map { it.capture() } + } else null + } + else -> null + } + + private fun IrExpression.isThisReceiver() = + this is IrGetValue && symbol.owner.name.asString() == "" + + private fun IrCall.inlineAtomicFunction(atomicType: IrType, accessors: List): IrCall { + val valueArguments = getValueArguments() + val functionName = getAtomicFunctionName() + val runtimeFunction = getRuntimeFunctionSymbol(functionName, atomicType) + return buildCall( + startOffset, endOffset, + target = runtimeFunction, + type = type, + typeArguments = if (runtimeFunction.owner.typeParameters.size == 1) listOf(atomicType) else emptyList(), + valueArguments = valueArguments + accessors + ) + } + + 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 && hasReceiverAccessorParameters() + + private fun getDeclarationWithAccessorParameters( + declaration: IrFunction, + extensionReceiverParameter: IrValueParameter? + ): IrSimpleFunction { + require(extensionReceiverParameter != null) + val paramsCount = declaration.valueParameters.size + val receiverType = extensionReceiverParameter.type.atomicToValueType() + return (declaration.parent as? IrDeclarationContainer)?.let { parent -> + parent.declarations.singleOrNull { + it is IrSimpleFunction && + it.name == declaration.symbol.owner.name && + it.valueParameters.size == paramsCount + 2 && + it.valueParameters.dropLast(2).withIndex() + .all { p -> p.value.render() == declaration.valueParameters[p.index].render() } && + it.valueParameters[paramsCount].name.asString() == GETTER && it.valueParameters[paramsCount + 1].name.asString() == SETTER && + it.getGetterReturnType()?.render() == receiverType.render() + } as? IrSimpleFunction + } ?: error( + "Failed to find the transformed atomic extension function with accessor parameters " + + "corresponding to the original declaration: ${declaration.render()} in the parent: ${declaration.parent.render()}" + ) + } + + private fun IrCall.isArrayElementGetter(): Boolean = + dispatchReceiver?.let { + it.type.isAtomicArrayType() && symbol.owner.name.asString() == GET + } ?: false + + private fun IrCall.getAccessors(): List { + val isArrayElement = isArrayElementGetter() + val valueType = type.atomicToValueType() + return listOf( + context.buildAccessorLambda(this, valueType, false, isArrayElement), + context.buildAccessorLambda(this, valueType, true, isArrayElement) + ) + } + + private fun getRuntimeFunctionSymbol(name: String, type: IrType): IrSimpleFunctionSymbol { + val functionName = when (name) { + "value." -> "getValue" + "value." -> "setValue" + else -> name + } + return context.referencePackageFunction(AFU_PKG, "$ATOMICFU_RUNTIME_FUNCTION_PREDICATE$functionName") { + val typeArg = it.owner.getGetterReturnType() + !(typeArg as IrType).isPrimitiveType() || typeArg == type + } + } + + private fun IrFunction.getGetterReturnType(): IrType? = + valueParameters.getOrNull(valueParameters.lastIndex - 1)?.let { getter -> + if (getter.name.asString() == GETTER) { + (getter.type as IrSimpleType).arguments.first().typeOrNull + } else null + } + + private fun IrCall.getAtomicFunctionName(): String = + symbol.signature?.let { signature -> + if (signature is IdSignature.AccessorSignature) signature.accessorSignature else signature.asPublic() + }?.declarationFqName?.let { name -> + if (name.substringBefore('.') in ATOMIC_VALUE_TYPES) { + name.substringAfter('.') + } else name + } ?: error("Incorrect pattern of the atomic function name: ${symbol.owner.render()}") + + private fun IrCall.eraseAtomicFactory() = + when { + isAtomicFactory() -> getValueArgument(0) ?: error("Atomic factory should take at least one argument: ${this.render()}") + isAtomicArrayFactory() -> buildObjectArray() + isReentrantLockFactory() -> context.buildConstNull() + else -> null + } + + private fun IrCall.buildObjectArray(): IrCall { + val arrayFactorySymbol = context.referencePackageFunction("kotlin", "arrayOfNulls") + val arrayElementType = getTypeArgument(0) ?: error("AtomicArray factory should have a type argument: ${symbol.owner.render()}") + val size = getValueArgument(0) + return buildCall( + startOffset, endOffset, + target = arrayFactorySymbol, + type = type, + typeArguments = listOf(arrayElementType), + valueArguments = listOf(size) + ) + } + } + + private fun IrFunction.isAtomicExtension(): Boolean = + extensionReceiverParameter?.let { it.type.isAtomicValueType() && this.isInline } ?: false + + private fun IrSymbol.isKotlinxAtomicfuPackage() = + this.isPublicApi && signature?.packageFqName()?.asString() == AFU_PKG + + private fun IrType.isAtomicValueType() = belongsTo(AFU_PKG, ATOMIC_VALUE_TYPES) + private fun IrType.isAtomicArrayType() = belongsTo(AFU_PKG, ATOMIC_ARRAY_TYPES) + private fun IrType.isReentrantLockType() = belongsTo(AFU_LOCKS_PKG, REENTRANT_LOCK_TYPE) + + private fun IrType.belongsTo(packageName: String, typeNames: Set) = + getSignature()?.let { sig -> + sig.packageFqName == packageName && sig.declarationFqName in typeNames + } ?: false + + private fun IrType.belongsTo(packageName: String, typeName: String) = + getSignature()?.let { sig -> + sig.packageFqName == packageName && sig.declarationFqName == typeName + } ?: false + + private fun IrType.getSignature(): CommonSignature? = classOrNull?.let { it.signature?.asPublic() } + + private fun IrType.atomicToValueType(): IrType { + require(this is IrSimpleType) + return classifier.signature?.asPublic()?.declarationFqName?.let { classId -> + if (classId == "AtomicRef") + arguments.first().typeOrNull ?: error("$AFU_PKG.AtomicRef type parameter is not IrTypeProjection") + else + AFU_CLASSES[classId] ?: error("IrType ${this.getClass()} does not match any of atomicfu types") + } ?: error("Unexpected signature of the atomic type: ${this.render()}") + } + + private fun IrCall.isAtomicFactory(): Boolean = + symbol.isKotlinxAtomicfuPackage() && symbol.owner.name.asString() == ATOMIC_VALUE_FACTORY && + type.isAtomicValueType() + + private fun IrCall.isAtomicArrayFactory(): Boolean = + symbol.isKotlinxAtomicfuPackage() && symbol.owner.name.asString() == ATOMIC_ARRAY_OF_NULLS_FACTORY && + type.isAtomicArrayType() + + private fun IrConstructorCall.isAtomicArrayConstructor(): Boolean = type.isAtomicArrayType() + + private fun IrCall.isReentrantLockFactory(): Boolean = + symbol.owner.name.asString() == REENTRANT_LOCK_FACTORY && type.isReentrantLockType() +} diff --git a/plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/TransformerUtil.kt b/plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/TransformerUtil.kt new file mode 100644 index 00000000000..972c5576093 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/src/org.jetbrains.kotlinx.atomicfu.compiler/extensions/TransformerUtil.kt @@ -0,0 +1,265 @@ +/* + * 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.IrStatement +import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET +import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder +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.* +import org.jetbrains.kotlin.ir.symbols.* +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.types.impl.* +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.types.Variance + +private const val KOTLIN = "kotlin" +private const val GET = "get" +private const val SET = "set" + +private val AFU_ARRAY_CLASSES: Map = mapOf( + "AtomicIntArray" to "IntArray", + "AtomicLongArray" to "LongArray", + "AtomicBooleanArray" to "BooleanArray", + "AtomicArray" to "Array" +) + +internal fun buildCall( + startOffset: Int, + endOffset: Int, + target: IrSimpleFunctionSymbol, + type: IrType? = null, + origin: IrStatementOrigin? = null, + typeArguments: List = emptyList(), + valueArguments: List = emptyList() +): IrCall = + IrCallImpl( + startOffset, + endOffset, + 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) } + } + } + +internal fun IrFactory.buildBlockBody(statements: List) = + createBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET, statements) + +internal fun buildSetField( + symbol: IrFieldSymbol, + receiver: IrExpression?, + value: IrExpression, + superQualifierSymbol: IrClassSymbol? = null +): IrSetField = + IrSetFieldImpl( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + symbol, + receiver, + value, + value.type, + IrStatementOrigin.GET_PROPERTY, + superQualifierSymbol + ) + +internal fun buildGetField( + symbol: IrFieldSymbol, + receiver: IrExpression?, + superQualifierSymbol: IrClassSymbol? = null, + type: IrType? = null +): IrGetField = + IrGetFieldImpl( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + symbol, + type ?: symbol.owner.type, + receiver, + IrStatementOrigin.GET_PROPERTY, + superQualifierSymbol + ) + +internal fun buildFunctionSimpleType( + symbol: IrClassifierSymbol, + typeParameters: List +): IrSimpleType = + IrSimpleTypeImpl( + classifier = symbol, + hasQuestionMark = false, + arguments = typeParameters.map { makeTypeProjection(it, Variance.INVARIANT) }, + annotations = emptyList() + ) + +internal fun buildGetValue( + startOffset: Int, + endOffset: Int, + symbol: IrValueSymbol +): IrGetValue = + IrGetValueImpl( + startOffset, + endOffset, + symbol.owner.type, + symbol + ) + +internal fun IrPluginContext.buildConstNull() = IrConstImpl.constNull(UNDEFINED_OFFSET, UNDEFINED_OFFSET, irBuiltIns.anyNType) + +internal fun getterName(getterCall: IrCall) = "" +internal fun setterName(getterCall: IrCall) = "" + +private fun Name.getFieldName() = "".toRegex().find(asString())?.groupValues?.get(1) + ?: error("Getter name ${this.asString()} does not match special name pattern ") + +internal fun IrFunctionAccessExpression.getValueArguments() = + (0 until valueArgumentsCount).map { i -> + getValueArgument(i) + } + +internal fun IrValueParameter.capture() = buildGetValue(UNDEFINED_OFFSET, UNDEFINED_OFFSET, symbol) + +internal fun IrPluginContext.buildGetterType(valueType: IrType): IrSimpleType = + buildFunctionSimpleType( + irBuiltIns.functionN(0).symbol, + listOf(valueType) + ) + +internal fun IrPluginContext.buildSetterType(valueType: IrType): IrSimpleType = + buildFunctionSimpleType( + irBuiltIns.functionN(1).symbol, + listOf(valueType, irBuiltIns.unitType) + ) + +private fun buildSetField(backingField: IrField, ownerClass: IrExpression?, value: IrGetValue): IrSetField { + val receiver = if (ownerClass is IrTypeOperatorCall) ownerClass.argument as IrGetValue else ownerClass + return buildSetField( + symbol = backingField.symbol, + 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( + symbol = backingField.symbol, + receiver = receiver + ) +} + +internal fun IrPluginContext.buildAccessorLambda( + getter: IrCall, + valueType: IrType, + isSetter: Boolean, + isArrayElement: Boolean +): IrExpression { + val getterCall = if (isArrayElement) getter.dispatchReceiver as IrCall else getter + val type = if (isSetter) buildSetterType(valueType) else buildGetterType(valueType) + val name = if (isSetter) setterName(getterCall) else getterName(getterCall) + val returnType = if (isSetter) irBuiltIns.unitType else valueType + val accessorFunction = irFactory.buildFun { + startOffset = UNDEFINED_OFFSET + endOffset = UNDEFINED_OFFSET + this.origin = IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR + this.name = Name.identifier(name) + this.visibility = DescriptorVisibilities.LOCAL + this.isInline = true + this.returnType = returnType + }.apply { + val valueParameter = JsIrBuilder.buildValueParameter(this, name, 0, valueType) + this.valueParameters = if (isSetter) listOf(valueParameter) else emptyList() + val body = if (isSetter) { + if (isArrayElement) { + val setSymbol = referenceFunction(referenceArrayClass(getterCall.type as IrSimpleType), SET) + val elementIndex = getter.getValueArgument(0)!!.deepCopyWithVariables() + buildCall( + UNDEFINED_OFFSET, UNDEFINED_OFFSET, + target = setSymbol, + type = 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(referenceArrayClass(getterCall.type as IrSimpleType), GET) + val elementIndex = getter.getValueArgument(0)!!.deepCopyWithVariables() + buildCall( + UNDEFINED_OFFSET, UNDEFINED_OFFSET, + target = getSymbol, + type = valueType, + origin = IrStatementOrigin.LAMBDA, + valueArguments = listOf(elementIndex) + ).apply { + dispatchReceiver = getField.deepCopyWithVariables() + } + } else { + getField.deepCopyWithVariables() + } + } + this.body = irFactory.buildBlockBody(listOf(body)) + origin = IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR + } + return IrFunctionExpressionImpl( + UNDEFINED_OFFSET, UNDEFINED_OFFSET, + type, + accessorFunction, + IrStatementOrigin.LAMBDA + ) +} + +private fun IrCall.getBackingField(): IrField { + val correspondingPropertySymbol = symbol.owner.correspondingPropertySymbol!! + return correspondingPropertySymbol.owner.backingField!! +} + +internal fun IrPluginContext.referencePackageFunction( + packageName: String, + name: String, + predicate: (IrFunctionSymbol) -> Boolean = { true } +) = try { + referenceFunctions(FqName("$packageName.$name")).single(predicate) + } catch (e: RuntimeException) { + error("Exception while looking for the function `$name` in package `$packageName`: ${e.message}") + } + +internal fun IrPluginContext.referenceFunction(classSymbol: IrClassSymbol, functionName: String): IrSimpleFunctionSymbol { + val functionId = FqName("$KOTLIN.${classSymbol.owner.name}.$functionName") + return try { + referenceFunctions(functionId).single() + } catch (e: RuntimeException) { + error("Exception while looking for the function `$functionId`: ${e.message}") + } +} + +private fun IrPluginContext.referenceArrayClass(irType: IrSimpleType): IrClassSymbol { + val afuClassId = (irType.classifier.signature!!.asPublic())!!.declarationFqName + val classId = FqName("$KOTLIN.${AFU_ARRAY_CLASSES[afuClassId]!!}") + return referenceClass(classId)!! +} + +internal fun IrPluginContext.getArrayConstructorSymbol(irType: IrSimpleType, predicate: (IrConstructorSymbol) -> Boolean = { true }): IrConstructorSymbol { + val afuClassId = (irType.classifier.signature!!.asPublic())!!.declarationFqName + val classId = FqName("$KOTLIN.${AFU_ARRAY_CLASSES[afuClassId]!!}") + return referenceConstructors(classId).single(predicate) +} diff --git a/plugins/atomicfu/atomicfu-compiler/test/org/jetbrains/kotlinx/atomicfu/AbstractAtomicfuJsIrTest.kt b/plugins/atomicfu/atomicfu-compiler/test/org/jetbrains/kotlinx/atomicfu/AbstractAtomicfuJsIrTest.kt new file mode 100644 index 00000000000..e4008ef3a31 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/test/org/jetbrains/kotlinx/atomicfu/AbstractAtomicfuJsIrTest.kt @@ -0,0 +1,45 @@ +/* + * 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.openapi.project.Project +import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension +import org.jetbrains.kotlin.js.test.ir.AbstractJsIrTest +import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.EnvironmentConfigurator +import org.jetbrains.kotlin.test.services.RuntimeClasspathProvider +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlinx.atomicfu.compiler.extensions.AtomicfuLoweringExtension +import java.io.File + +private val atomicfuCompileDependency = System.getProperty("atomicfu.classpath") +private val atomicfuRuntime = System.getProperty("atomicfuRuntimeForTests.classpath") + +open class AbstractAtomicfuJsIrTest : AbstractJsIrTest( + pathToTestDir = "plugins/atomicfu/atomicfu-compiler/testData/box/", + testGroupOutputDirPrefix = "box/" +) { + override fun configure(builder: TestConfigurationBuilder) { + super.configure(builder) + with(builder) { + useConfigurators(::AtomicfuEnvironmentConfigurator) + useCustomRuntimeClasspathProviders(::AtomicfuRuntimeClasspathProvider) + } + } +} + +class AtomicfuRuntimeClasspathProvider(testServices: TestServices) : RuntimeClasspathProvider(testServices) { + override fun runtimeClassPaths(module: TestModule): List { + return listOf(atomicfuCompileDependency, atomicfuRuntime).map { File(it) } + } +} + +class AtomicfuEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigurator(testServices) { + override fun registerCompilerExtensions(project: Project) { + IrGenerationExtension.registerExtension(project, AtomicfuLoweringExtension()) + } +} \ No newline at end of file diff --git a/plugins/atomicfu/atomicfu-compiler/test/org/jetbrains/kotlinx/atomicfu/AtomicfuJsIrTestGenerated.java b/plugins/atomicfu/atomicfu-compiler/test/org/jetbrains/kotlinx/atomicfu/AtomicfuJsIrTestGenerated.java new file mode 100644 index 00000000000..2b112fb2be4 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/test/org/jetbrains/kotlinx/atomicfu/AtomicfuJsIrTestGenerated.java @@ -0,0 +1,153 @@ +/* + * Copyright 2010-2021 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.util.KtTestUtil; +import org.jetbrains.kotlin.test.TargetBackend; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link GenerateNewCompilerTests.kt}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("plugins/atomicfu/atomicfu-compiler/testData/box") +@TestDataPath("$PROJECT_ROOT") +public class AtomicfuJsIrTestGenerated extends AbstractAtomicfuJsIrTest { + @Test + public void testAllFilesPresentInBox() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("plugins/atomicfu/atomicfu-compiler/testData/box"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR, true); + } + + @Test + @TestMetadata("ArithmeticTest.kt") + public void testArithmeticTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/ArithmeticTest.kt"); + } + + @Test + @TestMetadata("ArrayInlineFunctionTest.kt") + public void testArrayInlineFunctionTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/ArrayInlineFunctionTest.kt"); + } + + @Test + @TestMetadata("AtomicArrayTest.kt") + public void testAtomicArrayTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/AtomicArrayTest.kt"); + } + + @Test + @TestMetadata("ExtensionsTest.kt") + public void testExtensionsTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/ExtensionsTest.kt"); + } + + @Test + @TestMetadata("IndexArrayElementGetterTest.kt") + public void testIndexArrayElementGetterTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/IndexArrayElementGetterTest.kt"); + } + + @Test + @TestMetadata("InlineExtensionWithTypeParameterTest.kt") + public void testInlineExtensionWithTypeParameterTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/InlineExtensionWithTypeParameterTest.kt"); + } + + @Test + @TestMetadata("LockFreeIntBitsTest.kt") + public void testLockFreeIntBitsTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/LockFreeIntBitsTest.kt"); + } + + @Test + @TestMetadata("LockFreeLongCounterTest.kt") + public void testLockFreeLongCounterTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/LockFreeLongCounterTest.kt"); + } + + @Test + @TestMetadata("LockFreeQueueTest.kt") + public void testLockFreeQueueTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/LockFreeQueueTest.kt"); + } + + @Test + @TestMetadata("LockFreeStackTest.kt") + public void testLockFreeStackTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/LockFreeStackTest.kt"); + } + + @Test + @TestMetadata("LockTest.kt") + public void testLockTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/LockTest.kt"); + } + + @Test + @TestMetadata("LoopTest.kt") + public void testLoopTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/LoopTest.kt"); + } + + @Test + @TestMetadata("MultiInitTest.kt") + public void testMultiInitTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/MultiInitTest.kt"); + } + + @Test + @TestMetadata("ParameterizedInlineFunExtensionTest.kt") + public void testParameterizedInlineFunExtensionTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/ParameterizedInlineFunExtensionTest.kt"); + } + + @Test + @TestMetadata("PropertyDeclarationTest.kt") + public void testPropertyDeclarationTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/PropertyDeclarationTest.kt"); + } + + @Test + @TestMetadata("ReentrantLockTest.kt") + public void testReentrantLockTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/ReentrantLockTest.kt"); + } + + @Test + @TestMetadata("ScopeTest.kt") + public void testScopeTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/ScopeTest.kt"); + } + + @Test + @TestMetadata("SimpleLockTest.kt") + public void testSimpleLockTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/SimpleLockTest.kt"); + } + + @Test + @TestMetadata("SynchronizedObjectTest.kt") + public void testSynchronizedObjectTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/SynchronizedObjectTest.kt"); + } + + @Test + @TestMetadata("TopLevelTest.kt") + public void testTopLevelTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/TopLevelTest.kt"); + } + + @Test + @TestMetadata("UncheckedCastTest.kt") + public void testUncheckedCastTest() throws Exception { + runTest("plugins/atomicfu/atomicfu-compiler/testData/box/UncheckedCastTest.kt"); + } +} diff --git a/plugins/atomicfu/atomicfu-compiler/testData/box/ArithmeticTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/ArithmeticTest.kt new file mode 100644 index 00000000000..c9e3a634d69 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/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(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/box/ArrayInlineFunctionTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/ArrayInlineFunctionTest.kt new file mode 100644 index 00000000000..c9d0b1d1ddc --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/ArrayInlineFunctionTest.kt @@ -0,0 +1,47 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +class ArrayInlineFunctionTest { + private val anyArr = atomicArrayOfNulls(5) + private val refArr = atomicArrayOfNulls(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/box/AtomicArrayTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/AtomicArrayTest.kt new file mode 100644 index 00000000000..70e23e4c8ef --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/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(10) + val anyArr = atomicArrayOfNulls(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/box/ExtensionsTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/ExtensionsTest.kt new file mode 100644 index 00000000000..510f90de785 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/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(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.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/box/IndexArrayElementGetterTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/IndexArrayElementGetterTest.kt new file mode 100644 index 00000000000..69254e07707 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/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/box/InlineExtensionWithTypeParameterTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/InlineExtensionWithTypeParameterTest.kt new file mode 100644 index 00000000000..9965829c89c --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/InlineExtensionWithTypeParameterTest.kt @@ -0,0 +1,31 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +class InlineExtensionWithTypeParameterTest { + abstract class Segment>(val id: Int) + class SemaphoreSegment(id: Int) : Segment(id) + + private inline fun > AtomicRef.foo( + id: Int, + startFrom: S + ) { + startFrom.getSegmentId() + } + + private inline fun > S.getSegmentId(): Int { + var cur: S = this + return cur.id + } + + fun testInlineExtensionWithTypeParameter() { + val s = SemaphoreSegment(0) + val sref = atomic(s) + sref.foo(0, s) + } +} + +fun box(): String { + val testClass = InlineExtensionWithTypeParameterTest() + testClass.testInlineExtensionWithTypeParameter() + return "OK" +} \ No newline at end of file diff --git a/plugins/atomicfu/atomicfu-compiler/testData/box/LockFreeIntBitsTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/LockFreeIntBitsTest.kt new file mode 100644 index 00000000000..78d84a992b7 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/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/box/LockFreeLongCounterTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/LockFreeLongCounterTest.kt new file mode 100644 index 00000000000..853ca9e76d4 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/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/box/LockFreeQueueTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/LockFreeQueueTest.kt new file mode 100644 index 00000000000..ae36b81b4cf --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/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(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/box/LockFreeStackTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/LockFreeStackTest.kt new file mode 100644 index 00000000000..f4c674a5edf --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/LockFreeStackTest.kt @@ -0,0 +1,71 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +class LockFreeStackTest { + + fun testClear() { + val s = LockFreeStack() + check(s.isEmpty()) + s.pushLoop("A") + check(!s.isEmpty()) + s.clear() + check(s.isEmpty()) + } + + fun testPushPopLoop() { + val s = LockFreeStack() + check(s.isEmpty()) + s.pushLoop("A") + check(!s.isEmpty()) + check(s.popLoop() == "A") + check(s.isEmpty()) + } + + fun testPushPopUpdate() { + val s = LockFreeStack() + check(s.isEmpty()) + s.pushUpdate("A") + check(!s.isEmpty()) + check(s.popUpdate() == "A") + check(s.isEmpty()) + } +} + +class LockFreeStack { + private val top = atomic?>(null) + + private class Node(val value: T, val next: Node?) + + 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/box/LockTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/LockTest.kt new file mode 100644 index 00000000000..28afe7091b7 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/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 String.reflectionTest(mapParam: Map): List = 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/box/LoopTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/LoopTest.kt new file mode 100644 index 00000000000..b73d1d5b508 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/LoopTest.kt @@ -0,0 +1,82 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +class LoopTest { + private val a = atomic(0) + private val r = atomic(A("aaaa")) + private val rs = atomic("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 casLoopExpression(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.extensionLoopExpression(to: Int): Int = loop { cur -> + lazySet(cur + 10) + return if (compareAndSet(cur, to)) value else incrementAndGet() + } + + private inline fun AtomicInt.extensionLoopMixedReceivers(first: Int, second: Int): Int { + loop { cur -> + compareAndSet(cur, first) + a.compareAndSet(first, second) + return value + } + } + + private inline fun AtomicInt.extensionLoopRecursive(to: Int): Int { + loop { cur -> + compareAndSet(cur, to) + a.extensionLoop(5) + return value + } + } + + fun testIntExtensionLoops() { + assertEquals(5, casLoop(5)) + assertEquals(6, casLoopExpression(6)) + assertEquals(66, a.extensionLoop(66)) + assertEquals(77, a.extensionLoopExpression(777)) + assertEquals(99, a.extensionLoopMixedReceivers(88, 99)) + assertEquals(5, a.extensionLoopRecursive(100)) + } + + private inline fun AtomicRef.casLoop(to: String): String = loop { cur -> + if (compareAndSet(cur, A(to))) { + val res = value.s + return "${res}_AtomicRef" + } + } + + private inline fun AtomicRef.casLoop(to: String): String = loop { cur -> + if (compareAndSet(cur, to)) return "${value}_AtomicRef" + } + + fun testDeclarationWithEqualNames() { + check(r.casLoop("kk") == "kk_AtomicRef") + check(rs.casLoop("pp") == "pp_AtomicRef") + } +} + +fun box(): String { + val testClass = LoopTest() + testClass.testIntExtensionLoops() + testClass.testDeclarationWithEqualNames() + return "OK" +} \ No newline at end of file diff --git a/plugins/atomicfu/atomicfu-compiler/testData/box/MultiInitTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/MultiInitTest.kt new file mode 100644 index 00000000000..724f4b4b7a3 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/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/box/ParameterizedInlineFunExtensionTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/ParameterizedInlineFunExtensionTest.kt new file mode 100644 index 00000000000..d09e2d7abcd --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/ParameterizedInlineFunExtensionTest.kt @@ -0,0 +1,27 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +class ParameterizedInlineFunExtensionTest { + + private inline fun AtomicRef.foo(res1: S, res2: S, foo: (S) -> S): S { + val res = bar(res1, res2) + return res + } + + private inline fun AtomicRef.bar(res1: S, res2: 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/box/PropertyDeclarationTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/PropertyDeclarationTest.kt new file mode 100644 index 00000000000..3a240a45f6d --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/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 + private val lateIntArr: AtomicIntArray + private val lateRefArr: AtomicArray + private val lock: ReentrantLock + + init { + a = atomic(0) + head = atomic("AAA") + lateIntArr = AtomicIntArray(55) + lateRefArr = atomicArrayOfNulls(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/box/ReentrantLockTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/ReentrantLockTest.kt new file mode 100644 index 00000000000..e04d7ad488a --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/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/box/ScopeTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/ScopeTest.kt new file mode 100644 index 00000000000..3ffe02887de --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/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(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/box/SimpleLockTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/SimpleLockTest.kt new file mode 100644 index 00000000000..7f8d0ca15d2 --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/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 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/box/SynchronizedObjectTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/SynchronizedObjectTest.kt new file mode 100644 index 00000000000..98efc96d3da --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/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-compiler/testData/box/TopLevelTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/TopLevelTest.kt new file mode 100644 index 00000000000..22fe616936a --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/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(null) + +private val intArr = AtomicIntArray(3) +private val longArr = AtomicLongArray(5) +private val booleanArr = AtomicBooleanArray(4) +private val refArr = atomicArrayOfNulls>>(5) +private val anyRefArr = atomicArrayOfNulls(10) + +private val stringAtomicNullArr = atomicArrayOfNulls(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(val b: T) +data class BNode(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/box/UncheckedCastTest.kt b/plugins/atomicfu/atomicfu-compiler/testData/box/UncheckedCastTest.kt new file mode 100644 index 00000000000..d3fd78d5a3a --- /dev/null +++ b/plugins/atomicfu/atomicfu-compiler/testData/box/UncheckedCastTest.kt @@ -0,0 +1,54 @@ +import kotlinx.atomicfu.* +import kotlin.test.* + +private val topLevelS = atomic(arrayOf("A", "B")) + +class UncheckedCastTest { + private val s = atomic("AAA") + private val bs = atomic(null) + + @Suppress("UNCHECKED_CAST") + fun testAtomicValUncheckedCast() { + assertEquals((s as AtomicRef).value, "AAA") + bs.lazySet(arrayOf(arrayOf(Box(1), Box(2)))) + assertEquals((bs as AtomicRef>>).value[0]!![0].b * 10, 10) + } + + @Suppress("UNCHECKED_CAST") + fun testTopLevelValUnchekedCast() { + assertEquals((topLevelS as AtomicRef>).value[1], "B") + } + + private data class Box(val b: Int) + + @Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST") + private inline fun AtomicRef.getString(): String = + (this as AtomicRef).value + + fun testInlineFunc() { + assertEquals("AAA", s.getString()) + } + + private val a = atomicArrayOfNulls(10) + + fun testArrayValueUncheckedCast() { + a[0].value = "OK" + @Suppress("UNCHECKED_CAST") + assertEquals("OK", (a[0] as AtomicRef).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-runtime/build.gradle.kts b/plugins/atomicfu/atomicfu-runtime/build.gradle.kts new file mode 100644 index 00000000000..9a97b7e9858 --- /dev/null +++ b/plugins/atomicfu/atomicfu-runtime/build.gradle.kts @@ -0,0 +1,23 @@ +description = "Runtime library for the Atomicfu compiler plugin" + +plugins { + kotlin("js") +} + +group = "org.jetbrains.kotlin" + +repositories { + mavenCentral() +} + +kotlin { + js() + + sourceSets { + js().compilations["main"].defaultSourceSet { + dependencies { + compileOnly(kotlin("stdlib-js")) + } + } + } +} \ 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 00000000000..860acd16481 --- /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 00000000000..1b6fbc5c292 --- /dev/null +++ b/plugins/atomicfu/atomicfu-runtime/src/main/kotlin/atomicfu.kt @@ -0,0 +1,146 @@ +/* + * 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 + +/** + * Inline functions that are substituted instead of the corresponding atomic functions defined in `kotlinx.atomicfu` + * during Js/Ir transformation. + * + * Example of transformation: + * ``` + * val a = atomic(0) + * a.compareAndSet(expect, update) + * ``` + * is transformed to: + * ``` + * var a = 0 + * atomicfu_compareAndSet(expect, update, { return a }, { v: Int -> a.value = v }) + * ``` + */ + +internal inline fun atomicfu_getValue(`atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit): T { + return `atomicfu$getter`() +} + +internal inline fun atomicfu_setValue(value: T, `atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit): Unit { + `atomicfu$setter`(value) +} + +internal inline fun atomicfu_lazySet(value: T, `atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit): Unit { + `atomicfu$setter`(value) +} + +internal inline fun 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 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 atomicfu_loop(action: (T) -> Unit, `atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit): Nothing { + while (true) { + val cur = `atomicfu$getter`() + action(cur) + } +} + +internal inline fun 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 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 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 6f87d8feb08..85e0d580251 100644 --- a/settings.gradle +++ b/settings.gradle @@ -287,6 +287,9 @@ include ":benchmarks", ":kotlin-serialization-unshaded", ":wasm:wasm.ir" +include ":kotlinx-atomicfu-compiler-plugin", + ":kotlinx-atomicfu-runtime", + ":atomicfu" include ":compiler:fir", ":compiler:fir:cones", @@ -670,6 +673,10 @@ project(':kotlinx-serialization-compiler-plugin').projectDir = file("$rootDir/pl 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") + project(':kotlin-scripting-ide-services-unshaded').projectDir = "$rootDir/plugins/scripting/scripting-ide-services" as File project(':kotlin-scripting-ide-services-test').projectDir = "$rootDir/plugins/scripting/scripting-ide-services-test" as File project(':kotlin-scripting-ide-services').projectDir = "$rootDir/plugins/scripting/scripting-ide-services-embeddable" as File