Skip to content

Commit 5fe6c0d

Browse files
igoriakovlevskuzmichwoainikkmvicsokolovafzhinkin
authored
Introduce WebAssembly target (#334)
* support wasmJs and wasmWasi targets. * atomicfu-gradle-plugin does not transform wasm targets, and just provides an implementation dependency to the library. * tested application of the plugin to the mpp project with both wasm targets. --------- Co-authored-by: Svyatoslav Kuzmich <svyatoslav.kuzmich@jetbrains.com> Co-authored-by: Margarita Bobova <margarita.bobova@jetbrains.com> Co-authored-by: mvicsokolova <maria.sokolova@jetbrains.com> Co-authored-by: mvicsokolova <82594708+mvicsokolova@users.noreply.github.com> Co-authored-by: Filipp Zhinkin <filipp.zhinkin@gmail.com>
1 parent 9d2a3e4 commit 5fe6c0d

File tree

18 files changed

+170
-109
lines changed

18 files changed

+170
-109
lines changed

README.md

+4-5
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,11 @@ Starting from version `0.22.0` of the library your project is required to use:
4242

4343
## Features
4444

45+
* Complete multiplatform support: JVM, Native, JS and Wasm (since Kotlin 1.9.20).
4546
* Code it like a boxed value `atomic(0)`, but run it in production efficiently:
46-
* as `java.util.concurrent.atomic.AtomicXxxFieldUpdater` on Kotlin/JVM
47-
* 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
47+
* For **JVM**: an atomic value is represented as a plain value atomically updated with `java.util.concurrent.atomic.AtomicXxxFieldUpdater` from the Java standard library.
48+
* For **JS**: an atomic value is represented as a plain value.
49+
* For **Native** and **Wasm**: an atomic value is not transformed, it remains boxed, and `kotlinx-atomicfu` library is used as a runtime dependency.
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.

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

+10-4
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,15 @@ private fun Project.needsJvmIrTransformation(target: KotlinTarget): Boolean =
171171
rootProject.getBooleanProperty(ENABLE_JVM_IR_TRANSFORMATION) &&
172172
(target.platformType == KotlinPlatformType.jvm || target.platformType == KotlinPlatformType.androidJvm)
173173

174-
private fun KotlinTarget.isJsIrTarget() = (this is KotlinJsTarget && this.irTarget != null) || this is KotlinJsIrTarget
174+
private fun KotlinTarget.isJsIrTarget() =
175+
(this is KotlinJsTarget && this.irTarget != null) ||
176+
(this is KotlinJsIrTarget && this.platformType != KotlinPlatformType.wasm)
175177

176178
private fun Project.isTransformationDisabled(target: KotlinTarget): Boolean {
177179
val platformType = target.platformType
178180
return !config.transformJvm && (platformType == KotlinPlatformType.jvm || platformType == KotlinPlatformType.androidJvm) ||
179-
!config.transformJs && platformType == KotlinPlatformType.js
181+
!config.transformJs && platformType == KotlinPlatformType.js ||
182+
platformType == KotlinPlatformType.wasm
180183
}
181184

182185
// Adds kotlinx-atomicfu-runtime as an implementation dependency to the JS IR target:
@@ -286,8 +289,11 @@ private fun Project.configureJsTransformation() =
286289

287290
private fun Project.configureMultiplatformTransformation() =
288291
withKotlinTargets { target ->
289-
if (target.platformType == KotlinPlatformType.common || target.platformType == KotlinPlatformType.native) {
290-
return@withKotlinTargets // skip the common & native targets -- no transformation for them
292+
if (target.platformType == KotlinPlatformType.common ||
293+
target.platformType == KotlinPlatformType.native ||
294+
target.platformType == KotlinPlatformType.wasm
295+
) {
296+
return@withKotlinTargets // skip creation of transformation task for common, native and wasm targets
291297
}
292298
configureTransformationForTarget(target)
293299
}

atomicfu/build.gradle

+42-12
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
*/
44

55
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
6-
import static KotlinVersion.*
76

87
apply plugin: 'kotlin-multiplatform'
98
apply from: rootProject.file("gradle/targets.gradle")
@@ -20,10 +19,6 @@ ext {
2019
}
2120
}
2221

23-
// TODO: this block should be removed when Kotlin version is updated to 1.9.20
24-
// Right now it is used for conditional removal of native targets that will be removed in 1.9.20 (iosArm32, watchosX86)
25-
// and is necessary for testing of Kotlin dev builds.
26-
def enableDeprecatedNativeTargets = !isKotlinVersionAtLeast(ext.kotlin_version, 1, 9, 20)
2722

2823
kotlin {
2924
targets {
@@ -43,6 +38,15 @@ kotlin {
4338
// JVM -- always
4439
jvm()
4540

41+
// Wasm -- always
42+
wasmJs {
43+
nodejs()
44+
}
45+
46+
wasmWasi {
47+
nodejs()
48+
}
49+
4650
sourceSets {
4751
commonMain {
4852
dependencies {
@@ -55,7 +59,13 @@ kotlin {
5559
implementation 'org.jetbrains.kotlin:kotlin-test-annotations-common'
5660
}
5761
}
62+
63+
jsAndWasmSharedMain {
64+
dependsOn(sourceSets.commonMain)
65+
}
66+
5867
jsMain {
68+
dependsOn(sourceSets.jsAndWasmSharedMain)
5969
dependencies {
6070
implementation 'org.jetbrains.kotlin:kotlin-stdlib-js'
6171
}
@@ -65,6 +75,32 @@ kotlin {
6575
implementation 'org.jetbrains.kotlin:kotlin-test-js'
6676
}
6777
}
78+
79+
wasmJsMain {
80+
dependsOn(sourceSets.jsAndWasmSharedMain)
81+
dependencies {
82+
implementation 'org.jetbrains.kotlin:kotlin-stdlib-wasm-js'
83+
}
84+
}
85+
86+
wasmJsTest {
87+
dependencies {
88+
implementation 'org.jetbrains.kotlin:kotlin-test-wasm-js'
89+
}
90+
}
91+
92+
wasmWasiMain {
93+
dependsOn(sourceSets.jsAndWasmSharedMain)
94+
dependencies {
95+
implementation 'org.jetbrains.kotlin:kotlin-stdlib-wasm-wasi'
96+
}
97+
}
98+
wasmWasiTest {
99+
dependencies {
100+
implementation 'org.jetbrains.kotlin:kotlin-test-wasm-wasi'
101+
}
102+
}
103+
68104
jvmMain {
69105
dependencies {
70106
implementation 'org.jetbrains.kotlin:kotlin-stdlib'
@@ -116,12 +152,6 @@ if (rootProject.ext.native_targets_enabled) {
116152
addTarget(presets.androidNativeX64)
117153
addTarget(presets.mingwX64)
118154
addTarget(presets.watchosDeviceArm64)
119-
120-
// These targets will be removed in 1.9.20, remove them conditionally for the train builds
121-
if (enableDeprecatedNativeTargets) {
122-
addTarget(presets.iosArm32)
123-
addTarget(presets.watchosX86)
124-
}
125155
}
126156
}
127157

@@ -331,4 +361,4 @@ tasks.clean {
331361
setDelete(layout.buildDirectory.asFileTree.matching {
332362
exclude("tmp/.cache/expanded/expanded.lock")
333363
})
334-
}
364+
}

atomicfu/src/commonMain/kotlin/kotlinx/atomicfu/AtomicFU.common.kt

+13-14
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
package kotlinx.atomicfu
88

9-
import kotlin.js.JsName
109
import kotlin.internal.InlineOnly
1110
import kotlinx.atomicfu.TraceBase.None
1211
import kotlin.reflect.KProperty
@@ -110,7 +109,7 @@ public expect fun atomic(initial: Boolean): AtomicBoolean
110109
/**
111110
* Creates array of AtomicRef<T> of specified size, where each element is initialised with null value
112111
*/
113-
@JsName(ATOMIC_ARRAY_OF_NULLS)
112+
@OptionalJsName(ATOMIC_ARRAY_OF_NULLS)
114113
public fun <T> atomicArrayOfNulls(size: Int): AtomicArray<T?> = AtomicArray(size)
115114

116115
// ==================================== AtomicRef ====================================
@@ -508,15 +507,15 @@ public inline fun AtomicLong.updateAndGet(function: (Long) -> Long): Long {
508507
/**
509508
* Creates a new array of AtomicInt values of the specified size, where each element is initialised with 0
510509
*/
511-
@JsName(ATOMIC_INT_ARRAY)
510+
@OptionalJsName(ATOMIC_INT_ARRAY)
512511
public class AtomicIntArray(size: Int) {
513512
private val array = Array(size) { atomic(0) }
514513

515-
@JsName(ARRAY_SIZE)
514+
@OptionalJsName(ARRAY_SIZE)
516515
public val size: Int
517516
get() = array.size
518517

519-
@JsName(ARRAY_ELEMENT_GET)
518+
@OptionalJsName(ARRAY_ELEMENT_GET)
520519
public operator fun get(index: Int): AtomicInt = array[index]
521520
}
522521

@@ -525,15 +524,15 @@ public class AtomicIntArray(size: Int) {
525524
/**
526525
* Creates a new array of AtomicLong values of the specified size, where each element is initialised with 0L
527526
*/
528-
@JsName(ATOMIC_LONG_ARRAY)
527+
@OptionalJsName(ATOMIC_LONG_ARRAY)
529528
public class AtomicLongArray(size: Int) {
530529
private val array = Array(size) { atomic(0L) }
531530

532-
@JsName(ARRAY_SIZE)
531+
@OptionalJsName(ARRAY_SIZE)
533532
public val size: Int
534533
get() = array.size
535534

536-
@JsName(ARRAY_ELEMENT_GET)
535+
@OptionalJsName(ARRAY_ELEMENT_GET)
537536
public operator fun get(index: Int): AtomicLong = array[index]
538537
}
539538

@@ -542,29 +541,29 @@ public class AtomicLongArray(size: Int) {
542541
/**
543542
* Creates a new array of AtomicBoolean values of the specified size, where each element is initialised with false
544543
*/
545-
@JsName(ATOMIC_BOOLEAN_ARRAY)
544+
@OptionalJsName(ATOMIC_BOOLEAN_ARRAY)
546545
public class AtomicBooleanArray(size: Int) {
547546
private val array = Array(size) { atomic(false) }
548547

549-
@JsName(ARRAY_SIZE)
548+
@OptionalJsName(ARRAY_SIZE)
550549
public val size: Int
551550
get() = array.size
552551

553-
@JsName(ARRAY_ELEMENT_GET)
552+
@OptionalJsName(ARRAY_ELEMENT_GET)
554553
public operator fun get(index: Int): AtomicBoolean = array[index]
555554
}
556555

557556

558557
// ==================================== AtomicArray ====================================
559558

560-
@JsName(ATOMIC_REF_ARRAY)
559+
@OptionalJsName(ATOMIC_REF_ARRAY)
561560
public class AtomicArray<T> internal constructor(size: Int) {
562561
private val array = Array(size) { atomic<T?>(null) }
563562

564-
@JsName(ARRAY_SIZE)
563+
@OptionalJsName(ARRAY_SIZE)
565564
public val size: Int
566565
get() = array.size
567566

568-
@JsName(ARRAY_ELEMENT_GET)
567+
@OptionalJsName(ARRAY_ELEMENT_GET)
569568
public operator fun get(index: Int): AtomicRef<T?> = array[index]
570569
}

atomicfu/src/commonMain/kotlin/kotlinx/atomicfu/MangledJsNames.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package kotlinx.atomicfu
22

33
/**
4-
* All atomicfu declarations are annotated with [@JsName][kotlin.js.JsName] to have specific names in JS output.
4+
* All atomicfu declarations are annotated with [@OptionalJsName][kotlin.js.JsName] to have specific names in JS output.
55
* JS output transformer relies on these mangled names to erase all atomicfu references.
66
*/
77

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package kotlinx.atomicfu
2+
3+
/**
4+
* This annotation actualized with JsName in JS platform and not actualized in others.
5+
*/
6+
@OptIn(ExperimentalMultiplatform::class)
7+
@OptionalExpectation
8+
@Retention(AnnotationRetention.BINARY)
9+
@Target(
10+
AnnotationTarget.CLASS,
11+
AnnotationTarget.FUNCTION,
12+
AnnotationTarget.PROPERTY,
13+
AnnotationTarget.CONSTRUCTOR,
14+
AnnotationTarget.PROPERTY_GETTER,
15+
AnnotationTarget.PROPERTY_SETTER
16+
)
17+
expect annotation class OptionalJsName(val name: String)

atomicfu/src/commonMain/kotlin/kotlinx/atomicfu/Trace.common.kt

+5-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
package kotlinx.atomicfu
88

9-
import kotlin.js.JsName
109
import kotlin.internal.InlineOnly
1110

1211
/**
@@ -66,30 +65,30 @@ public expect val traceFormatDefault: TraceFormat
6665
/**
6766
* Base class for implementations of `Trace`.
6867
*/
69-
@JsName(TRACE_BASE_CONSTRUCTOR)
68+
@OptionalJsName(TRACE_BASE_CONSTRUCTOR)
7069
public open class TraceBase internal constructor() {
7170
/**
7271
* Accepts the logging [event] and appends it to the trace.
7372
*/
74-
@JsName(TRACE_APPEND_1)
73+
@OptionalJsName(TRACE_APPEND_1)
7574
public open fun append(event: Any) {}
7675

7776
/**
7877
* Accepts the logging events [event1], [event2] and appends them to the trace.
7978
*/
80-
@JsName(TRACE_APPEND_2)
79+
@OptionalJsName(TRACE_APPEND_2)
8180
public open fun append(event1: Any, event2: Any) {}
8281

8382
/**
8483
* Accepts the logging events [event1], [event2], [event3] and appends them to the trace.
8584
*/
86-
@JsName(TRACE_APPEND_3)
85+
@OptionalJsName(TRACE_APPEND_3)
8786
public open fun append(event1: Any, event2: Any, event3: Any) {}
8887

8988
/**
9089
* Accepts the logging events [event1], [event2], [event3], [event4] and appends them to the trace.
9190
*/
92-
@JsName(TRACE_APPEND_4)
91+
@OptionalJsName(TRACE_APPEND_4)
9392
public open fun append(event1: Any, event2: Any, event3: Any, event4: Any) {}
9493

9594
/**

atomicfu/src/commonMain/kotlin/kotlinx/atomicfu/TraceFormat.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ import kotlin.js.JsName
1212
/**
1313
* Trace string formatter.
1414
*/
15-
@JsName(TRACE_FORMAT_CLASS)
15+
@OptionalJsName(TRACE_FORMAT_CLASS)
1616
public open class TraceFormat {
1717
/**
1818
* Formats trace at the given [index] with the given [event] of Any type.
1919
*/
20-
@JsName(TRACE_FORMAT_FORMAT_FUNCTION)
20+
@OptionalJsName(TRACE_FORMAT_FORMAT_FUNCTION)
2121
public open fun format(index: Int, event: Any): String = "$index: $event"
2222
}
2323

0 commit comments

Comments
 (0)