Skip to content

Commit 28c7762

Browse files
committed
Support a flag to enable Native IR transformations
1 parent 9d2a3e4 commit 28c7762

File tree

5 files changed

+117
-24
lines changed

5 files changed

+117
-24
lines changed

README.md

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,8 @@ Starting from version `0.22.0` of the library your project is required to use:
4545
* Code it like a boxed value `atomic(0)`, but run it in production efficiently:
4646
* as `java.util.concurrent.atomic.AtomicXxxFieldUpdater` on Kotlin/JVM
4747
* as a plain unboxed value on Kotlin/JS
48-
* Multiplatform: write common Kotlin code with atomics that compiles for Kotlin JVM, JS, and Native backends:
49-
* Compile-only dependency for JVM and JS (no runtime dependencies)
50-
* Compile and runtime dependency for Kotlin/Native
48+
* as Kotlin/Native atomic intrinsics on Kotlin/Native
49+
* Multiplatform: write common Kotlin code with atomics that compiles for Kotlin JVM, JS, and Native backends.
5150
* Use Kotlin-specific extensions (e.g. inline `loop`, `update`, `updateAndGet` functions).
5251
* Use atomic arrays, user-defined extensions on atomics and locks (see [more features](#more-features)).
5352
* [Tracing operations](#tracing-operations) for debugging.
@@ -248,17 +247,13 @@ public var foo: T by _foo // public delegated property (val/var)
248247
(more specifically, `complex_expression` should not have branches in its compiled representation).
249248
Extract `complex_expression` into a variable when needed.
250249

251-
## Transformation modes
250+
## Atomicfu compiler plugin
252251

253-
Basically, Atomicfu library provides an effective usage of atomic values by performing the transformations of the compiled code.
254-
For JVM and JS there 2 transformation modes available:
255-
* **Post-compilation transformation** that modifies the compiled bytecode or `*.js` files.
256-
* **IR transformation** that is performed by the atomicfu compiler plugin.
257-
258-
### Atomicfu compiler plugin
259-
260-
Compiler plugin transformation is less fragile than transformation of the compiled sources
261-
as it depends on the compiler IR tree.
252+
To provide a user-friendly atomic API on the frontend and effective usage of atomic values on the backend kotlinx-atomicfu library uses the compiler plugin to transform
253+
IR for all the target backends:
254+
* **JVM**: atomics are replaced with `java.util.concurrent.atomic.AtomicXxxFieldUpdater`.
255+
* **Native**: atomics are implemented via atomic intrinsics on Kotlin/Native.
256+
* **JS**: atomics are unboxed and represented as plain values.
262257

263258
To turn on IR transformation set these properties in your `gradle.properties` file:
264259

@@ -267,6 +262,7 @@ To turn on IR transformation set these properties in your `gradle.properties` fi
267262

268263
```groovy
269264
kotlinx.atomicfu.enableJvmIrTransformation=true // for JVM IR transformation
265+
kotlinx.atomicfu.enableNativeIrTransformation=true // for Native IR transformation
270266
kotlinx.atomicfu.enableJsIrTransformation=true // for JS IR transformation
271267
```
272268

atomicfu-gradle-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/gradle/AtomicFUGradlePlugin.kt

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ private const val TEST_IMPLEMENTATION_CONFIGURATION = "testImplementation"
3535
private const val ENABLE_JS_IR_TRANSFORMATION_LEGACY = "kotlinx.atomicfu.enableIrTransformation"
3636
private const val ENABLE_JS_IR_TRANSFORMATION = "kotlinx.atomicfu.enableJsIrTransformation"
3737
private const val ENABLE_JVM_IR_TRANSFORMATION = "kotlinx.atomicfu.enableJvmIrTransformation"
38+
private const val ENABLE_NATIVE_IR_TRANSFORMATION = "kotlinx.atomicfu.enableNativeIrTransformation"
3839
private const val MIN_SUPPORTED_GRADLE_VERSION = "7.0"
3940
private const val MIN_SUPPORTED_KGP_VERSION = "1.7.0"
4041

@@ -78,6 +79,7 @@ private fun Project.applyAtomicfuCompilerPlugin() {
7879
extensions.getByType(AtomicfuKotlinGradleSubplugin.AtomicfuKotlinGradleExtension::class.java).apply {
7980
isJsIrTransformationEnabled = rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION)
8081
isJvmIrTransformationEnabled = rootProject.getBooleanProperty(ENABLE_JVM_IR_TRANSFORMATION)
82+
isNativeIrTransformationEnabled = rootProject.getBooleanProperty(ENABLE_NATIVE_IR_TRANSFORMATION)
8183
}
8284
} else {
8385
// for KGP >= 1.6.20 && KGP <= 1.7.20:
@@ -171,12 +173,17 @@ private fun Project.needsJvmIrTransformation(target: KotlinTarget): Boolean =
171173
rootProject.getBooleanProperty(ENABLE_JVM_IR_TRANSFORMATION) &&
172174
(target.platformType == KotlinPlatformType.jvm || target.platformType == KotlinPlatformType.androidJvm)
173175

176+
private fun Project.needsNativeIrTransformation(target: KotlinTarget): Boolean =
177+
rootProject.getBooleanProperty(ENABLE_NATIVE_IR_TRANSFORMATION) &&
178+
(target.platformType == KotlinPlatformType.native)
179+
174180
private fun KotlinTarget.isJsIrTarget() = (this is KotlinJsTarget && this.irTarget != null) || this is KotlinJsIrTarget
175181

176182
private fun Project.isTransformationDisabled(target: KotlinTarget): Boolean {
177183
val platformType = target.platformType
178184
return !config.transformJvm && (platformType == KotlinPlatformType.jvm || platformType == KotlinPlatformType.androidJvm) ||
179-
!config.transformJs && platformType == KotlinPlatformType.js
185+
!config.transformJs && platformType == KotlinPlatformType.js ||
186+
!needsNativeIrTransformation(target) && platformType == KotlinPlatformType.native
180187
}
181188

182189
// Adds kotlinx-atomicfu-runtime as an implementation dependency to the JS IR target:
@@ -277,27 +284,33 @@ private fun Project.configureTasks() {
277284

278285
private fun Project.configureJvmTransformation() {
279286
if (kotlinExtension is KotlinJvmProjectExtension || kotlinExtension is KotlinAndroidProjectExtension) {
280-
configureTransformationForTarget((kotlinExtension as KotlinSingleTargetExtension<*>).target)
287+
val target = (kotlinExtension as KotlinSingleTargetExtension<*>).target
288+
if (!needsJvmIrTransformation(target)) {
289+
configureTransformationForTarget(target)
290+
}
281291
}
282292
}
283293

284-
private fun Project.configureJsTransformation() =
285-
configureTransformationForTarget((kotlinExtension as KotlinJsProjectExtension).js())
294+
private fun Project.configureJsTransformation() {
295+
val target = (kotlinExtension as KotlinJsProjectExtension).js()
296+
if (!needsJsIrTransformation(target)) {
297+
configureTransformationForTarget(target)
298+
}
299+
}
286300

287301
private fun Project.configureMultiplatformTransformation() =
288302
withKotlinTargets { target ->
289-
if (target.platformType == KotlinPlatformType.common || target.platformType == KotlinPlatformType.native) {
290-
return@withKotlinTargets // skip the common & native targets -- no transformation for them
291-
}
303+
// Skip transformation for common and native targets and in case IR transformation by the compiler plugin is enabled (for JVM or JS targets)
304+
if (target.platformType == KotlinPlatformType.common || target.platformType == KotlinPlatformType.native ||
305+
needsJvmIrTransformation(target) || needsJsIrTransformation(target))
306+
return@withKotlinTargets
292307
configureTransformationForTarget(target)
293308
}
294309

295310
private fun Project.configureTransformationForTarget(target: KotlinTarget) {
296311
val originalDirsByCompilation = hashMapOf<KotlinCompilation<*>, FileCollection>()
297312
val config = config
298313
target.compilations.all compilations@{ compilation ->
299-
// do not modify directories if compiler plugin is applied
300-
if (needsJvmIrTransformation(target) || needsJsIrTransformation(target)) return@compilations
301314
val compilationType = compilation.name.compilationNameToType()
302315
?: return@compilations // skip unknown compilations
303316
val classesDirs = compilation.output.classesDirs
@@ -323,7 +336,7 @@ private fun Project.configureTransformationForTarget(target: KotlinTarget) {
323336
val transformTask = when (target.platformType) {
324337
KotlinPlatformType.jvm, KotlinPlatformType.androidJvm -> {
325338
// create transformation task only if transformation is required and JVM IR compiler transformation is not enabled
326-
if (config.transformJvm && !rootProject.getBooleanProperty(ENABLE_JVM_IR_TRANSFORMATION)) {
339+
if (config.transformJvm) {
327340
project.registerJvmTransformTask(compilation)
328341
.configureJvmTask(
329342
compilation.compileDependencyFiles,

integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/cases/MppProjectTest.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,22 @@ class MppProjectTest {
4141
assert(mppSample.cleanAndBuild().isSuccessful)
4242
mppSample.checkConsumableDependencies()
4343
}
44+
45+
@Test
46+
fun testMppNativeWithEnabledIrTransformation() {
47+
mppSample.enableNativeIrTransformation = true
48+
assert(mppSample.cleanAndBuild().isSuccessful)
49+
mppSample.checkMppNativeCompileOnlyDependencies()
50+
// TODO: klib checks are skipped for now because of this problem KT-61143
51+
//mppSample.buildAndCheckNativeKlib()
52+
}
53+
54+
@Test
55+
fun testMppNativeWithDisabledIrTransformation() {
56+
mppSample.enableNativeIrTransformation = false
57+
assert(mppSample.cleanAndBuild().isSuccessful)
58+
mppSample.checkMppNativeImplementationDependencies()
59+
// TODO: klib checks are skipped for now because of this problem KT-61143
60+
//mppSample.buildAndCheckNativeKlib()
61+
}
4462
}

integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/framework/checker/ArtifactChecker.kt

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ import kotlinx.atomicfu.gradle.plugin.test.framework.runner.GradleBuild
88
import kotlinx.atomicfu.gradle.plugin.test.framework.runner.cleanAndBuild
99
import org.objectweb.asm.*
1010
import java.io.File
11+
import java.net.URLClassLoader
1112
import kotlin.test.assertFalse
1213

1314
internal abstract class ArtifactChecker(private val targetDir: File) {
1415

1516
private val ATOMIC_FU_REF = "Lkotlinx/atomicfu/".toByteArray()
1617
protected val KOTLIN_METADATA_DESC = "Lkotlin/Metadata;"
1718

18-
private val projectName = targetDir.name.substringBeforeLast("-")
19+
protected val projectName = targetDir.name.substringBeforeLast("-")
1920

2021
val buildDir
2122
get() = targetDir.resolve("build").also {
@@ -60,8 +61,60 @@ private class BytecodeChecker(targetDir: File) : ArtifactChecker(targetDir) {
6061
}
6162
}
6263

64+
private class KlibChecker(targetDir: File) : ArtifactChecker(targetDir) {
65+
66+
val nativeJar = System.getProperty("kotlin.native.jar")
67+
68+
val classLoader: ClassLoader = URLClassLoader(arrayOf(File(nativeJar).toURI().toURL()), this.javaClass.classLoader)
69+
70+
private fun invokeKlibTool(
71+
kotlinNativeClassLoader: ClassLoader?,
72+
klibFile: File,
73+
functionName: String,
74+
hasOutput: Boolean,
75+
vararg args: Any
76+
): String {
77+
val libraryClass = Class.forName("org.jetbrains.kotlin.cli.klib.Library", true, kotlinNativeClassLoader)
78+
val entryPoint = libraryClass.declaredMethods.single { it.name == functionName }
79+
val lib = libraryClass.getDeclaredConstructor(String::class.java, String::class.java, String::class.java)
80+
.newInstance(klibFile.canonicalPath, null, "host")
81+
82+
val output = StringBuilder()
83+
84+
// This is a hack. It would be better to get entryPoint properly
85+
if (args.isNotEmpty()) {
86+
entryPoint.invoke(lib, output, *args)
87+
} else if (hasOutput) {
88+
entryPoint.invoke(lib, output)
89+
} else {
90+
entryPoint.invoke(lib)
91+
}
92+
return output.toString()
93+
}
94+
95+
override fun checkReferences() {
96+
val myKlib = buildDir.resolve("classes/kotlin/macosX64/main/klib/$projectName.klib")
97+
require(myKlib.exists()) { "Native klib is not found: ${myKlib.path}" }
98+
val klibIr = invokeKlibTool(
99+
kotlinNativeClassLoader = classLoader,
100+
klibFile = myKlib,
101+
functionName = "ir",
102+
hasOutput = true,
103+
false
104+
)
105+
assertFalse(klibIr.toByteArray().findAtomicfuRef(), "Found kotlinx/atomicfu in klib ${myKlib.path}:\n $klibIr")
106+
}
107+
}
108+
63109
internal fun GradleBuild.buildAndCheckBytecode() {
64110
val buildResult = cleanAndBuild()
65111
require(buildResult.isSuccessful) { "Build of the project $projectName failed:\n ${buildResult.output}" }
66112
BytecodeChecker(this.targetDir).checkReferences()
67113
}
114+
115+
// TODO: klib checks are skipped for now because of this problem KT-61143
116+
internal fun GradleBuild.buildAndCheckNativeKlib() {
117+
val buildResult = cleanAndBuild()
118+
require(buildResult.isSuccessful) { "Build of the project $projectName failed:\n ${buildResult.output}" }
119+
KlibChecker(this.targetDir).checkReferences()
120+
}

integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/framework/checker/DependenciesChecker.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,19 @@ internal fun GradleBuild.checkMppJvmCompileOnlyDependencies() {
5050
checkAtomicfuDependencyIsAbsent(listOf("jvmRuntimeClasspath", "jvmApiElements", "jvmRuntimeElements"), commonAtomicfuDependency)
5151
}
5252

53+
// Checks Native target of an MPP project
54+
internal fun GradleBuild.checkMppNativeCompileOnlyDependencies() {
55+
// Here the name of the native target is hardcoded because the tested mpp-sample project declares this target and
56+
// KGP generates the same set of depependencies for every declared native target ([mingwX64|linuxX64|macosX64...]CompileKlibraries)
57+
checkAtomicfuDependencyIsPresent(listOf("macosX64CompileKlibraries"), commonAtomicfuDependency)
58+
checkAtomicfuDependencyIsAbsent(listOf("macosX64MainImplementation"), commonAtomicfuDependency)
59+
}
60+
61+
// Checks Native target of an MPP project
62+
internal fun GradleBuild.checkMppNativeImplementationDependencies() {
63+
checkAtomicfuDependencyIsPresent(listOf("macosX64CompileKlibraries", "macosX64MainImplementation"), commonAtomicfuDependency)
64+
}
65+
5366
// Some dependencies may be not resolvable but consumable and will not be present in the output of :dependencies task,
5467
// in this case we should check .pom or .module file of the published project.
5568
// This method checks if the .module file in the sample project publication contains org.jetbrains.kotlinx:atomicfu dependency included.

0 commit comments

Comments
 (0)