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
This commit is contained in:
mvicsokolova
2021-12-01 22:33:13 +03:00
committed by GitHub
parent 05695761ec
commit 93561a1a55
45 changed files with 2655 additions and 4 deletions
@@ -314,7 +314,7 @@ class ClassicFrontendFacade(
dependentDescriptors: List<ModuleDescriptorImpl>,
friendsDescriptors: List<ModuleDescriptorImpl>,
): 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)
@@ -137,13 +137,17 @@ class JsEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigu
return getMainModule(testServices).name
}
fun getStdlibPathsForModule(module: TestModule): List<String> {
fun getRuntimePathsForModule(module: TestModule, testServices: TestServices): List<String> {
val result = mutableListOf<String>()
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<ModuleDescriptorImpl> {
+2
View File
@@ -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()) {
@@ -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<String>) {
System.setProperty("java.awt.headless", "true")
@@ -395,5 +396,15 @@ fun main(args: Array<String>) {
model("box")
}
}
testGroup(
"plugins/atomicfu/atomicfu-compiler/test",
"plugins/atomicfu/atomicfu-compiler/testData",
testRunnerMethodName = "runTest0"
) {
testClass<AbstractAtomicfuJsIrTest> {
model("box/")
}
}
}
}
+5
View File
@@ -7258,6 +7258,11 @@
<sha256 value="7a6bd3e09a63d6a57725cc3016465d2148b8febb82a6b165b33956f8a15e0b94" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="atomicfu-js" version="0.16.3">
<artifact name="atomicfu-jsir-0.16.3.klib">
<sha256 value="8b2ab53f08ee262fe2bea798011b3bda230b039fc14c1d7e2ab888f4384a9194" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="org.jetbrains.kotlinx" name="binary-compatibility-validator" version="0.7.1">
<artifact name="binary-compatibility-validator-0.7.1.jar">
<md5 value="f188e849f683da58684012bbdc732abc" origin="Generated by Gradle"/>
@@ -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)
+1
View File
@@ -3,6 +3,7 @@ def pluginProjects = [
project(':kotlin-allopen'),
project(':kotlin-noarg'),
project(':kotlin-serialization'),
project(':atomicfu'),
project(':kotlin-lombok')
]
configure(pluginProjects) { project ->
+40
View File
@@ -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'
}
}
}
@@ -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<AbstractCompile> {
companion object {
const val ATOMICFU_ARTIFACT_NAME = "atomicfu"
}
override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean = true
override fun applyToCompilation(
kotlinCompilation: KotlinCompilation<*>
): Provider<List<SubpluginOption>> =
kotlinCompilation.target.project.provider { emptyList<SubpluginOption>() }
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<SubpluginOption> {
return emptyList()
}
//endregion
}
@@ -0,0 +1 @@
implementation-class=org.jetbrains.kotlinx.atomicfu.gradle.AtomicfuKotlinGradleSubplugin
@@ -0,0 +1 @@
implementation-class=org.jetbrains.kotlinx.atomicfu.gradle.AtomicfuKotlinGradleSubplugin
@@ -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
@@ -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/")
}
@@ -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
@@ -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()) }
}
}
@@ -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)
}
}
@@ -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<String, IrType> = 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<IrDeclaration>.addAllTransformedAtomicExtensions() {
val transformedDeclarations = mutableListOf<IrDeclaration>()
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<T>.foo(arg) { ... } -> inline fun <T> 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<IrFunction?> {
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<Any>("AAA")
// (a as AtomicRef<String>).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<IrExpression>? =
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() == "<this>"
private fun IrCall.inlineAtomicFunction(atomicType: IrType, accessors: List<IrExpression>): 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<IrExpression> {
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.<get-value>" -> "getValue"
"value.<set-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<String>) =
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()
}
@@ -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<String, String> = 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<IrType> = emptyList(),
valueArguments: List<IrExpression?> = 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<IrStatement>) =
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<IrType>
): 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) = "<get-${getterCall.symbol.owner.name.getFieldName()}>"
internal fun setterName(getterCall: IrCall) = "<set-${getterCall.symbol.owner.name.getFieldName()}>"
private fun Name.getFieldName() = "<get-(\\w+)>".toRegex().find(asString())?.groupValues?.get(1)
?: error("Getter name ${this.asString()} does not match special name pattern <get-fieldName>")
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)
}
@@ -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<File> {
return listOf(atomicfuCompileDependency, atomicfuRuntime).map { File(it) }
}
}
class AtomicfuEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigurator(testServices) {
override fun registerCompilerExtensions(project: Project) {
IrGenerationExtension.registerExtension(project, AtomicfuLoweringExtension())
}
}
@@ -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");
}
}
@@ -0,0 +1,131 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class IntArithmetic {
val _x = atomic(0)
val x get() = _x.value
}
class LongArithmetic {
val _x = atomic(4294967296)
val x get() = _x.value
val y = atomic(5000000000)
val z = atomic(2424920024888888848)
val max = atomic(9223372036854775807)
}
class BooleanArithmetic {
val _x = atomic(false)
val x get() = _x.value
}
class ReferenceArithmetic {
val _x = atomic<String?>(null)
}
class ArithmeticTest {
val local = atomic(0)
fun testGetValue() {
val a = IntArithmetic()
a._x.value = 5
check(a._x.value == 5)
var aValue = a._x.value
check(aValue == 5)
check(a.x == 5)
local.value = 555
aValue = local.value
check(local.value == aValue)
}
fun testAtomicCallPlaces(): Boolean {
val a = IntArithmetic()
a._x.value = 5
a._x.compareAndSet(5, 42)
val res = a._x.compareAndSet(42, 45)
check(res)
check(a._x.compareAndSet(45, 77))
check(!a._x.compareAndSet(95, 77))
return a._x.compareAndSet(77, 88)
}
fun testInt() {
val a = IntArithmetic()
check(a.x == 0)
val update = 3
check(a._x.getAndSet(update) == 0)
check(a._x.compareAndSet(update, 8))
a._x.lazySet(1)
check(a.x == 1)
check(a._x.getAndSet(2) == 1)
check(a.x == 2)
check(a._x.getAndIncrement() == 2)
check(a.x == 3)
check(a._x.getAndDecrement() == 3)
check(a.x == 2)
check(a._x.getAndAdd(2) == 2)
check(a.x == 4)
check(a._x.addAndGet(3) == 7)
check(a.x == 7)
check(a._x.incrementAndGet() == 8)
check(a.x == 8)
check(a._x.decrementAndGet() == 7)
check(a.x == 7)
check(a._x.compareAndSet(7, 10))
}
fun testLong() {
val a = LongArithmetic()
check(a.z.value == 2424920024888888848)
a.z.lazySet(8424920024888888848)
check(a.z.value == 8424920024888888848)
check(a.z.getAndSet(8924920024888888848) == 8424920024888888848)
check(a.z.value == 8924920024888888848)
check(a.z.incrementAndGet() == 8924920024888888849) // fails
check(a.z.value == 8924920024888888849)
check(a.z.getAndDecrement() == 8924920024888888849)
check(a.z.value == 8924920024888888848)
check(a.z.getAndAdd(100000000000000000) == 8924920024888888848)
check(a.z.value == 9024920024888888848)
check(a.z.addAndGet(-9223372036854775807) == -198452011965886959)
check(a.z.value == -198452011965886959)
check(a.z.incrementAndGet() == -198452011965886958)
check(a.z.value == -198452011965886958)
check(a.z.decrementAndGet() == -198452011965886959)
check(a.z.value == -198452011965886959)
}
fun testBoolean() {
val a = BooleanArithmetic()
check(!a.x)
a._x.lazySet(true)
check(a.x)
check(a._x.getAndSet(true))
check(a._x.compareAndSet(true, false))
check(!a.x)
}
fun testReference() {
val a = ReferenceArithmetic()
a._x.value = "aaa"
check(a._x.value == "aaa")
a._x.lazySet("bb")
check(a._x.value == "bb")
check(a._x.getAndSet("ccc") == "bb")
check(a._x.value == "ccc")
}
}
fun box(): String {
val testClass = ArithmeticTest()
testClass.testGetValue()
if (!testClass.testAtomicCallPlaces()) return "testAtomicCallPlaces: FAILED"
testClass.testInt()
testClass.testLong()
testClass.testBoolean()
testClass.testReference()
return "OK"
}
@@ -0,0 +1,47 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class ArrayInlineFunctionTest {
private val anyArr = atomicArrayOfNulls<Any?>(5)
private val refArr = atomicArrayOfNulls<Box>(5)
private data class Box(val n: Int)
fun testSetArrayElementValueInLoop() {
anyArr[0].loop { cur ->
assertTrue(anyArr[0].compareAndSet(cur, IntArray(5)))
return
}
}
private fun action(cur: Box?) = cur?.let { Box(cur.n * 10) }
fun testArrayElementUpdate() {
refArr[0].lazySet(Box(5))
refArr[0].update { cur -> cur?.let { Box(cur.n * 10) } }
assertEquals(refArr[0].value!!.n, 50)
}
fun testArrayElementGetAndUpdate() {
refArr[0].lazySet(Box(5))
assertEquals(refArr[0].getAndUpdate { cur -> action(cur) }!!.n, 5)
assertEquals(refArr[0].value!!.n, 50)
}
fun testArrayElementUpdateAndGet() {
refArr[0].lazySet(Box(5))
assertEquals(refArr[0].updateAndGet { cur -> action(cur) }!!.n, 50)
}
}
fun box(): String {
val testClass = ArrayInlineFunctionTest()
testClass.testSetArrayElementValueInLoop()
testClass.testArrayElementGetAndUpdate()
testClass.testArrayElementUpdate()
testClass.testArrayElementUpdateAndGet()
return "OK"
}
@@ -0,0 +1,97 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class AtomicArrayTest {
fun testIntArray() {
val A = AtomicArrayClass()
check(A.intArr[0].compareAndSet(0, 3))
check(A.intArr[1].value == 0)
A.intArr[0].lazySet(5)
check(A.intArr[0].value + A.intArr[1].value + A.intArr[2].value == 5)
check(A.intArr[0].compareAndSet(5, 10))
check(A.intArr[0].getAndDecrement() == 10)
check(A.intArr[0].value == 9)
A.intArr[2].value = 2
check(A.intArr[2].value == 2)
check(A.intArr[2].compareAndSet(2, 34))
check(A.intArr[2].value == 34)
}
fun testLongArray() {
val A = AtomicArrayClass()
A.longArr[0].value = 2424920024888888848
check(A.longArr[0].value == 2424920024888888848)
A.longArr[0].lazySet(8424920024888888848)
check(A.longArr[0].value == 8424920024888888848)
val ac = A.longArr[0].value
A.longArr[3].value = ac
check(A.longArr[3].getAndSet(8924920024888888848) == 8424920024888888848)
check(A.longArr[3].value == 8924920024888888848)
val ac1 = A.longArr[3].value
A.longArr[4].value = ac1
check(A.longArr[4].incrementAndGet() == 8924920024888888849)
check(A.longArr[4].value == 8924920024888888849)
check(A.longArr[4].getAndDecrement() == 8924920024888888849)
check(A.longArr[4].value == 8924920024888888848)
A.longArr[4].value = 8924920024888888848
check(A.longArr[4].getAndAdd(100000000000000000) == 8924920024888888848)
val ac2 = A.longArr[4].value
A.longArr[1].value = ac2
check(A.longArr[1].value == 9024920024888888848)
check(A.longArr[1].addAndGet(-9223372036854775807) == -198452011965886959)
check(A.longArr[1].value == -198452011965886959)
check(A.longArr[1].incrementAndGet() == -198452011965886958)
check(A.longArr[1].value == -198452011965886958)
check(A.longArr[1].decrementAndGet() == -198452011965886959)
check(A.longArr[1].value == -198452011965886959)
}
fun testBooleanArray() {
val A = AtomicArrayClass()
check(!A.booleanArr[1].value)
A.booleanArr[1].compareAndSet(false, true)
A.booleanArr[0].lazySet(true)
check(!A.booleanArr[2].getAndSet(true))
check(A.booleanArr[0].value && A.booleanArr[1].value && A.booleanArr[2].value)
A.booleanArr[0].value = false
check(!A.booleanArr[0].value)
}
fun testRefArray() {
val A = AtomicArrayClass()
val a2 = ARef(2)
val a3 = ARef(3)
A.refArr[0].value = a2
check(A.refArr[0].value!!.n == 2)
check(A.refArr[0].compareAndSet(a2, a3))
check(A.refArr[0].value!!.n == 3)
val r0 = A.refArr[0].value
A.refArr[3].value = r0
check(A.refArr[3].value!!.n == 3)
val a = A.a.value
check(A.refArr[3].compareAndSet(a3, a))
}
}
class AtomicArrayClass {
val intArr = AtomicIntArray(10)
val longArr = AtomicLongArray(10)
val booleanArr = AtomicBooleanArray(10)
val refArr = atomicArrayOfNulls<ARef>(10)
val anyArr = atomicArrayOfNulls<Any?>(10)
val a = atomic(ARef(8))
}
data class ARef(val n: Int)
fun box(): String {
val testClass = AtomicArrayTest()
testClass.testIntArray()
testClass.testLongArray()
testClass.testBooleanArray()
testClass.testRefArray()
return "OK"
}
@@ -0,0 +1,115 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class ExtensionsTest {
val a = atomic(0)
val l = atomic(0L)
val s = atomic<String?>(null)
val b = atomic(true)
fun testScopedFieldGetters() {
check(a.value == 0)
val update = 3
a.lazySet(update)
check(a.compareAndSet(update, 8))
a.lazySet(1)
check(a.value == 1)
check(a.getAndSet(2) == 1)
check(a.value == 2)
check(a.getAndIncrement() == 2)
check(a.value == 3)
check(a.getAndDecrement() == 3)
check(a.value == 2)
check(a.getAndAdd(2) == 2)
check(a.value == 4)
check(a.addAndGet(3) == 7)
check(a.value == 7)
check(a.incrementAndGet() == 8)
check(a.value == 8)
check(a.decrementAndGet() == 7)
check(a.value == 7)
check(a.compareAndSet(7, 10))
}
inline fun AtomicInt.intExtensionArithmetic() {
value = 0
check(value == 0)
val update = 3
lazySet(update)
check(compareAndSet(update, 8))
lazySet(1)
check(value == 1)
check(getAndSet(2) == 1)
check(value == 2)
check(getAndIncrement() == 2)
check(value == 3)
check(getAndDecrement() == 3)
check(value == 2)
check(getAndAdd(2) == 2)
check(value == 4)
check(addAndGet(3) == 7)
check(value == 7)
check(incrementAndGet() == 8)
check(value == 8)
check(decrementAndGet() == 7)
check(value == 7)
check(compareAndSet(7, 10))
check(compareAndSet(value, 55))
check(value == 55)
}
inline fun AtomicLong.longExtensionArithmetic() {
value = 2424920024888888848
check(value == 2424920024888888848)
lazySet(8424920024888888848)
check(value == 8424920024888888848)
check(getAndSet(8924920024888888848) == 8424920024888888848)
check(value == 8924920024888888848)
check(incrementAndGet() == 8924920024888888849) // fails
check(value == 8924920024888888849)
check(getAndDecrement() == 8924920024888888849)
check(value == 8924920024888888848)
check(getAndAdd(100000000000000000) == 8924920024888888848)
check(value == 9024920024888888848)
check(addAndGet(-9223372036854775807) == -198452011965886959)
check(value == -198452011965886959)
check(incrementAndGet() == -198452011965886958)
check(value == -198452011965886958)
check(decrementAndGet() == -198452011965886959)
check(value == -198452011965886959)
}
inline fun AtomicRef<String?>.refExtension() {
value = "aaa"
check(value == "aaa")
lazySet("bb")
check(value == "bb")
check(getAndSet("ccc") == "bb")
check(value == "ccc")
}
inline fun AtomicBoolean.booleanExtensionArithmetic() {
value = false
check(!value)
lazySet(true)
check(value)
check(getAndSet(true))
check(compareAndSet(value, false))
check(!value)
}
fun testExtension() {
a.intExtensionArithmetic()
l.longExtensionArithmetic()
s.refExtension()
b.booleanExtensionArithmetic()
}
}
fun box(): String {
val testClass = ExtensionsTest()
testClass.testScopedFieldGetters()
testClass.testExtension()
return "OK"
}
@@ -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"
}
@@ -0,0 +1,31 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class InlineExtensionWithTypeParameterTest {
abstract class Segment<S : Segment<S>>(val id: Int)
class SemaphoreSegment(id: Int) : Segment<SemaphoreSegment>(id)
private inline fun <S : Segment<S>> AtomicRef<S>.foo(
id: Int,
startFrom: S
) {
startFrom.getSegmentId()
}
private inline fun <S : Segment<S>> S.getSegmentId(): Int {
var cur: S = this
return cur.id
}
fun testInlineExtensionWithTypeParameter() {
val s = SemaphoreSegment(0)
val sref = atomic(s)
sref.foo(0, s)
}
}
fun box(): String {
val testClass = InlineExtensionWithTypeParameterTest()
testClass.testInlineExtensionWithTypeParameter()
return "OK"
}
@@ -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"
}
@@ -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"
}
@@ -0,0 +1,55 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class LockFreeQueueTest {
fun testBasic() {
val q = LockFreeQueue()
check(q.dequeue() == -1)
q.enqueue(42)
check(q.dequeue() == 42)
check(q.dequeue() == -1)
q.enqueue(1)
q.enqueue(2)
check(q.dequeue() == 1)
check(q.dequeue() == 2)
check(q.dequeue() == -1)
}
}
// MS-queue
public class LockFreeQueue {
private val head = atomic(Node(0))
private val tail = atomic(head.value)
private class Node(val value: Int) {
val next = atomic<Node?>(null)
}
public fun enqueue(value: Int) {
val node = Node(value)
tail.loop { curTail ->
val curNext = curTail.next.value
if (curNext != null) {
tail.compareAndSet(curTail, curNext)
return@loop
}
if (curTail.next.compareAndSet(null, node)) {
tail.compareAndSet(curTail, node)
return
}
}
}
public fun dequeue(): Int {
head.loop { curHead ->
val next = curHead.next.value ?: return -1
if (head.compareAndSet(curHead, next)) return next.value
}
}
}
fun box(): String {
val testClass = LockFreeQueueTest()
testClass.testBasic()
return "OK"
}
@@ -0,0 +1,71 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class LockFreeStackTest {
fun testClear() {
val s = LockFreeStack<String>()
check(s.isEmpty())
s.pushLoop("A")
check(!s.isEmpty())
s.clear()
check(s.isEmpty())
}
fun testPushPopLoop() {
val s = LockFreeStack<String>()
check(s.isEmpty())
s.pushLoop("A")
check(!s.isEmpty())
check(s.popLoop() == "A")
check(s.isEmpty())
}
fun testPushPopUpdate() {
val s = LockFreeStack<String>()
check(s.isEmpty())
s.pushUpdate("A")
check(!s.isEmpty())
check(s.popUpdate() == "A")
check(s.isEmpty())
}
}
class LockFreeStack<T> {
private val top = atomic<Node<T>?>(null)
private class Node<T>(val value: T, val next: Node<T>?)
fun isEmpty() = top.value == null
fun clear() { top.value = null }
fun pushLoop(value: T) {
top.loop { cur ->
val upd = Node(value, cur)
if (top.compareAndSet(cur, upd)) return
}
}
fun popLoop(): T? {
top.loop { cur ->
if (cur == null) return null
if (top.compareAndSet(cur, cur.next)) return cur.value
}
}
fun pushUpdate(value: T) {
top.update { cur -> Node(value, cur) }
}
fun popUpdate(): T? =
top.getAndUpdate { cur -> cur?.next } ?.value
}
fun box(): String {
val testClass = LockFreeStackTest()
testClass.testClear()
testClass.testPushPopLoop()
testClass.testPushPopUpdate()
return "OK"
}
@@ -0,0 +1,29 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class LockTest {
private val inProgressLock = atomic(false)
fun testLock() {
var result = ""
if (inProgressLock.tryAcquire()) {
result = "OK"
}
assertEquals("OK", result)
}
}
// This function will be removed by transformer
@Suppress("NOTHING_TO_INLINE")
private inline fun AtomicBoolean.tryAcquire(): Boolean = compareAndSet(false, true)
// This function is here to test if the Kotlin metadata still consistent after transform
// It is used in ReflectionTest, DO NOT REMOVE
@Suppress("UNUSED_PARAMETER")
fun <AA, BB : Number> String.reflectionTest(mapParam: Map<in AA, BB>): List<BB> = error("no impl")
fun box(): String {
val testClass = LockTest()
testClass.testLock()
return "OK"
}
@@ -0,0 +1,82 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class LoopTest {
private val a = atomic(0)
private val r = atomic<A>(A("aaaa"))
private val rs = atomic<String>("bbbb")
private class A(val s: String)
private inline fun casLoop(to: Int): Int {
a.loop { cur ->
if (a.compareAndSet(cur, to)) return a.value
return 777
}
}
private inline fun 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<A>.casLoop(to: String): String = loop { cur ->
if (compareAndSet(cur, A(to))) {
val res = value.s
return "${res}_AtomicRef<A>"
}
}
private inline fun AtomicRef<String>.casLoop(to: String): String = loop { cur ->
if (compareAndSet(cur, to)) return "${value}_AtomicRef<String>"
}
fun testDeclarationWithEqualNames() {
check(r.casLoop("kk") == "kk_AtomicRef<A>")
check(rs.casLoop("pp") == "pp_AtomicRef<String>")
}
}
fun box(): String {
val testClass = LoopTest()
testClass.testIntExtensionLoops()
testClass.testDeclarationWithEqualNames()
return "OK"
}
@@ -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"
}
@@ -0,0 +1,27 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class ParameterizedInlineFunExtensionTest {
private inline fun <S> AtomicRef<S>.foo(res1: S, res2: S, foo: (S) -> S): S {
val res = bar(res1, res2)
return res
}
private inline fun <S> AtomicRef<S>.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"
}
@@ -0,0 +1,34 @@
import kotlinx.atomicfu.*
import kotlinx.atomicfu.locks.*
import kotlin.test.*
class PropertyDeclarationTest {
private val a: AtomicInt
private val head: AtomicRef<String>
private val lateIntArr: AtomicIntArray
private val lateRefArr: AtomicArray<String?>
private val lock: ReentrantLock
init {
a = atomic(0)
head = atomic("AAA")
lateIntArr = AtomicIntArray(55)
lateRefArr = atomicArrayOfNulls<String?>(10)
lock = reentrantLock()
}
fun test() {
assertEquals(0, a.value)
check(head.compareAndSet("AAA", "BBB"))
assertEquals("BBB", head.value)
assertEquals(0, lateIntArr[35].value)
assertEquals(null, lateRefArr[5].value)
assertEquals(null, lock)
}
}
fun box(): String {
val testClass = PropertyDeclarationTest()
testClass.test()
return "OK"
}
@@ -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"
}
@@ -0,0 +1,45 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class AA(val value: Int) {
val b = B(value + 1)
val c = C(D(E(value + 1)))
fun updateToB(affected: Any): Boolean {
(affected as AtomicState).state.compareAndSet(this, b)
return (affected.state.value is B && (affected.state.value as B).value == value + 1)
}
fun manyProperties(affected: Any): Boolean {
(affected as AtomicState).state.compareAndSet(this, c.d.e)
return (affected.state.value is E && (affected.state.value as E).x == value + 1)
}
}
class B (val value: Int)
class C (val d: D)
class D (val e: E)
class E (val x: Int)
class AtomicState(value: Any) {
val state = atomic<Any?>(value)
}
class ScopeTest {
fun scopeTest() {
val a = AA(0)
val affected: Any = AtomicState(a)
check(a.updateToB(affected))
val a1 = AA(0)
val affected1: Any = AtomicState(a1)
check(a1.manyProperties(affected1))
}
}
fun box(): String {
val testClass = ScopeTest()
testClass.scopeTest()
return "OK"
}
@@ -0,0 +1,36 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class SimpleLockTest {
fun withLock() {
val lock = SimpleLock()
val result = lock.withLock {
"OK"
}
assertEquals("OK", result)
}
}
class SimpleLock {
private val _locked = atomic(0)
fun <T> withLock(block: () -> T): T {
// this contrieves construct triggers Kotlin compiler to reuse local variable slot #2 for
// the exception in `finally` clause
try {
_locked.loop { locked ->
check(locked == 0)
if (!_locked.compareAndSet(0, 1)) return@loop // continue
return block()
}
} finally {
_locked.value = 0
}
}
}
fun box(): String {
val testClass = SimpleLockTest()
testClass.withLock()
return "OK"
}
@@ -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"
}
@@ -0,0 +1,178 @@
import kotlinx.atomicfu.*
import kotlin.test.*
private val a = atomic(0)
private val b = atomic(2424920024888888848)
private val c = atomic(true)
private val abcNode = atomic(ANode(BNode(CNode(8))))
private val any = atomic<Any?>(null)
private val intArr = AtomicIntArray(3)
private val longArr = AtomicLongArray(5)
private val booleanArr = AtomicBooleanArray(4)
private val refArr = atomicArrayOfNulls<ANode<BNode<CNode>>>(5)
private val anyRefArr = atomicArrayOfNulls<Any>(10)
private val stringAtomicNullArr = atomicArrayOfNulls<String>(10)
class TopLevelPrimitiveTest {
fun testTopLevelInt() {
a.value
check(a.value == 0)
check(a.getAndSet(3) == 0)
check(a.compareAndSet(3, 8))
a.lazySet(1)
check(a.value == 1)
check(a.getAndSet(2) == 1)
check(a.value == 2)
check(a.getAndIncrement() == 2)
check(a.value == 3)
check(a.getAndDecrement() == 3)
check(a.value == 2)
check(a.getAndAdd(2) == 2)
check(a.value == 4)
check(a.addAndGet(3) == 7)
check(a.value == 7)
check(a.incrementAndGet() == 8)
check(a.value == 8)
check(a.decrementAndGet() == 7)
check(a.value == 7)
a.compareAndSet(7, 10)
}
fun testTopLevelLong() {
check(b.value == 2424920024888888848)
b.lazySet(8424920024888888848)
check(b.value == 8424920024888888848)
check(b.getAndSet(8924920024888888848) == 8424920024888888848)
check(b.value == 8924920024888888848)
check(b.incrementAndGet() == 8924920024888888849)
check(b.value == 8924920024888888849)
check(b.getAndDecrement() == 8924920024888888849)
check(b.value == 8924920024888888848)
check(b.getAndAdd(100000000000000000) == 8924920024888888848)
check(b.value == 9024920024888888848)
check(b.addAndGet(-9223372036854775807) == -198452011965886959)
check(b.value == -198452011965886959)
check(b.incrementAndGet() == -198452011965886958)
check(b.value == -198452011965886958)
check(b.decrementAndGet() == -198452011965886959)
check(b.value == -198452011965886959)
}
fun testTopLevelBoolean() {
check(c.value)
c.lazySet(false)
check(!c.value)
check(!c.getAndSet(true))
check(c.compareAndSet(true, false))
check(!c.value)
}
fun testTopLevelRef() {
check(abcNode.value.b.c.d == 8)
val newNode = ANode(BNode(CNode(76)))
check(abcNode.getAndSet(newNode).b.c.d == 8)
check(abcNode.value.b.c.d == 76)
val l = IntArray(4){i -> i}
any.lazySet(l)
check((any.value as IntArray)[2] == 2)
}
fun testTopLevelArrayOfNulls() {
check(stringAtomicNullArr[0].value == null)
check(stringAtomicNullArr[0].compareAndSet(null, "aa"))
stringAtomicNullArr[1].lazySet("aa")
check(stringAtomicNullArr[0].value == stringAtomicNullArr[1].value)
}
}
class TopLevelArrayTest {
fun testIntArray() {
check(intArr[0].compareAndSet(0, 3))
check(intArr[1].value == 0)
intArr[0].lazySet(5)
check(intArr[0].value + intArr[1].value + intArr[2].value == 5)
check(intArr[0].compareAndSet(5, 10))
check(intArr[0].getAndDecrement() == 10)
check(intArr[0].value == 9)
intArr[2].value = 2
check(intArr[2].value == 2)
check(intArr[2].compareAndSet(2, 34))
check(intArr[2].value == 34)
}
fun testLongArray() {
longArr[0].value = 2424920024888888848
check(longArr[0].value == 2424920024888888848)
longArr[0].lazySet(8424920024888888848)
check(longArr[0].value == 8424920024888888848)
val ac = longArr[0].value
longArr[3].value = ac
check(longArr[3].getAndSet(8924920024888888848) == 8424920024888888848)
check(longArr[3].value == 8924920024888888848)
val ac1 = longArr[3].value
longArr[4].value = ac1
check(longArr[4].incrementAndGet() == 8924920024888888849)
check(longArr[4].value == 8924920024888888849)
check(longArr[4].getAndDecrement() == 8924920024888888849)
check(longArr[4].value == 8924920024888888848)
longArr[4].value = 8924920024888888848
check(longArr[4].getAndAdd(100000000000000000) == 8924920024888888848)
val ac2 = longArr[4].value
longArr[1].value = ac2
check(longArr[1].value == 9024920024888888848)
check(longArr[1].addAndGet(-9223372036854775807) == -198452011965886959)
check(longArr[1].value == -198452011965886959)
check(longArr[1].incrementAndGet() == -198452011965886958)
check(longArr[1].value == -198452011965886958)
check(longArr[1].decrementAndGet() == -198452011965886959)
check(longArr[1].value == -198452011965886959)
}
fun testBooleanArray() {
check(!booleanArr[1].value)
booleanArr[1].compareAndSet(false, true)
booleanArr[0].lazySet(true)
check(!booleanArr[2].getAndSet(true))
check(booleanArr[0].value && booleanArr[1].value && booleanArr[2].value)
}
@Suppress("UNCHECKED_CAST")
fun testRefArray() {
val a2 = ANode(BNode(CNode(2)))
val a3 = ANode(BNode(CNode(3)))
refArr[0].value = a2
check(refArr[0].value!!.b.c.d == 2)
check(refArr[0].compareAndSet(a2, a3))
check(refArr[0].value!!.b.c.d == 3)
val r0 = refArr[0].value
refArr[3].value = r0
check(refArr[3].value!!.b.c.d == 3)
val a = abcNode.value
check(refArr[3].compareAndSet(a3, a))
}
}
data class ANode<T>(val b: T)
data class BNode<T>(val c: T)
data class CNode(val d: Int)
fun box(): String {
val primitiveTest = TopLevelPrimitiveTest()
primitiveTest.testTopLevelInt()
primitiveTest.testTopLevelLong()
primitiveTest.testTopLevelBoolean()
primitiveTest.testTopLevelRef()
primitiveTest.testTopLevelArrayOfNulls()
val arrayTest = TopLevelArrayTest()
arrayTest.testIntArray()
arrayTest.testLongArray()
arrayTest.testBooleanArray()
arrayTest.testRefArray()
return "OK"
}
@@ -0,0 +1,54 @@
import kotlinx.atomicfu.*
import kotlin.test.*
private val topLevelS = atomic<Any>(arrayOf("A", "B"))
class UncheckedCastTest {
private val s = atomic<Any>("AAA")
private val bs = atomic<Any?>(null)
@Suppress("UNCHECKED_CAST")
fun testAtomicValUncheckedCast() {
assertEquals((s as AtomicRef<String>).value, "AAA")
bs.lazySet(arrayOf(arrayOf(Box(1), Box(2))))
assertEquals((bs as AtomicRef<Array<Array<Box>>>).value[0]!![0].b * 10, 10)
}
@Suppress("UNCHECKED_CAST")
fun testTopLevelValUnchekedCast() {
assertEquals((topLevelS as AtomicRef<Array<String>>).value[1], "B")
}
private data class Box(val b: Int)
@Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST")
private inline fun <T> AtomicRef<T>.getString(): String =
(this as AtomicRef<String>).value
fun testInlineFunc() {
assertEquals("AAA", s.getString())
}
private val a = atomicArrayOfNulls<Any?>(10)
fun testArrayValueUncheckedCast() {
a[0].value = "OK"
@Suppress("UNCHECKED_CAST")
assertEquals("OK", (a[0] as AtomicRef<String>).value)
}
fun testArrayValueUncheckedCastInlineFunc() {
a[0].value = "OK"
assertEquals("OK", a[0].getString())
}
}
fun box(): String {
val testClass = UncheckedCastTest()
testClass.testTopLevelValUnchekedCast()
testClass.testArrayValueUncheckedCast()
testClass.testArrayValueUncheckedCastInlineFunc()
testClass.testAtomicValUncheckedCast()
testClass.testInlineFunc()
return "OK"
}
@@ -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"))
}
}
}
}
@@ -0,0 +1 @@
kotlin.js.compiler=both
@@ -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 <T> atomicfu_getValue(`atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit): T {
return `atomicfu$getter`()
}
internal inline fun <T> atomicfu_setValue(value: T, `atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit): Unit {
`atomicfu$setter`(value)
}
internal inline fun <T> atomicfu_lazySet(value: T, `atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit): Unit {
`atomicfu$setter`(value)
}
internal inline fun <T> atomicfu_compareAndSet(expect: T, update: T, `atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit): Boolean {
if (`atomicfu$getter`() == expect) {
`atomicfu$setter`(update)
return true
} else {
return false
}
}
internal inline fun <T> atomicfu_getAndSet(value: T, `atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit): T {
val oldValue = `atomicfu$getter`()
`atomicfu$setter`(value)
return oldValue
}
internal inline fun atomicfu_getAndIncrement(`atomicfu$getter`: () -> Int, `atomicfu$setter`: (Int) -> Unit): Int {
val oldValue = `atomicfu$getter`()
`atomicfu$setter`(oldValue + 1)
return oldValue
}
internal inline fun atomicfu_getAndIncrement(`atomicfu$getter`: () -> Long, `atomicfu$setter`: (Long) -> Unit): Long {
val oldValue = `atomicfu$getter`()
`atomicfu$setter`(oldValue + 1)
return oldValue
}
internal inline fun atomicfu_incrementAndGet(`atomicfu$getter`: () -> Int, `atomicfu$setter`: (Int) -> Unit): Int {
`atomicfu$setter`(`atomicfu$getter`() + 1)
return `atomicfu$getter`()
}
internal inline fun atomicfu_incrementAndGet(`atomicfu$getter`: () -> Long, `atomicfu$setter`: (Long) -> Unit): Long {
`atomicfu$setter`(`atomicfu$getter`() + 1)
return `atomicfu$getter`()
}
internal inline fun atomicfu_getAndDecrement(`atomicfu$getter`: () -> Int, `atomicfu$setter`: (Int) -> Unit): Int {
val oldValue = `atomicfu$getter`()
`atomicfu$setter`(oldValue - 1)
return oldValue
}
internal inline fun atomicfu_getAndDecrement(`atomicfu$getter`: () -> Long, `atomicfu$setter`: (Long) -> Unit): Long {
val oldValue = `atomicfu$getter`()
`atomicfu$setter`(oldValue - 1)
return oldValue
}
internal inline fun atomicfu_decrementAndGet(`atomicfu$getter`: () -> Int, `atomicfu$setter`: (Int) -> Unit): Int {
`atomicfu$setter`(`atomicfu$getter`() - 1)
return `atomicfu$getter`()
}
internal inline fun atomicfu_decrementAndGet(`atomicfu$getter`: () -> Long, `atomicfu$setter`: (Long) -> Unit): Long {
`atomicfu$setter`(`atomicfu$getter`() - 1)
return `atomicfu$getter`()
}
internal inline fun atomicfu_getAndAdd(value: Int, `atomicfu$getter`: () -> Int, `atomicfu$setter`: (Int) -> Unit): Int {
val oldValue = `atomicfu$getter`()
`atomicfu$setter`(oldValue + value)
return oldValue
}
internal inline fun atomicfu_getAndAdd(value: Long, `atomicfu$getter`: () -> Long, `atomicfu$setter`: (Long) -> Unit): Long {
val oldValue = `atomicfu$getter`()
`atomicfu$setter`(oldValue + value)
return oldValue
}
internal inline fun atomicfu_addAndGet(value: Int, `atomicfu$getter`: () -> Int, `atomicfu$setter`: (Int) -> Unit): Int {
`atomicfu$setter`(`atomicfu$getter`() + value)
return `atomicfu$getter`()
}
internal inline fun atomicfu_addAndGet(value: Long, `atomicfu$getter`: () -> Long, `atomicfu$setter`: (Long) -> Unit): Long {
`atomicfu$setter`(`atomicfu$getter`() + value)
return `atomicfu$getter`()
}
internal inline fun <T> atomicfu_loop(action: (T) -> Unit, `atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit): Nothing {
while (true) {
val cur = `atomicfu$getter`()
action(cur)
}
}
internal inline fun <T> atomicfu_update(function: (T) -> T, `atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit) {
while (true) {
val cur = `atomicfu$getter`()
val upd = function(cur)
if (atomicfu_compareAndSet(cur, upd, `atomicfu$getter`, `atomicfu$setter`)) return
}
}
internal inline fun <T> atomicfu_getAndUpdate(function: (T) -> T, `atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit): T {
while (true) {
val cur = `atomicfu$getter`()
val upd = function(cur)
if (atomicfu_compareAndSet(cur, upd, `atomicfu$getter`, `atomicfu$setter`)) return cur
}
}
internal inline fun <T> atomicfu_updateAndGet(function: (T) -> T, `atomicfu$getter`: () -> T, `atomicfu$setter`: (T) -> Unit): T {
while (true) {
val cur = `atomicfu$getter`()
val upd = function(cur)
if (atomicfu_compareAndSet(cur, upd, `atomicfu$getter`, `atomicfu$setter`)) return upd
}
}
+7
View File
@@ -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