Skip to content

Commit

Permalink
Introduce WebAssembly target (#334)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
6 people authored Nov 14, 2023
1 parent 9d2a3e4 commit 5fe6c0d
Show file tree
Hide file tree
Showing 18 changed files with 170 additions and 109 deletions.
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,11 @@ Starting from version `0.22.0` of the library your project is required to use:

## Features

* Complete multiplatform support: JVM, Native, JS and Wasm (since Kotlin 1.9.20).
* Code it like a boxed value `atomic(0)`, but run it in production efficiently:
* as `java.util.concurrent.atomic.AtomicXxxFieldUpdater` on Kotlin/JVM
* as a plain unboxed value on Kotlin/JS
* Multiplatform: write common Kotlin code with atomics that compiles for Kotlin JVM, JS, and Native backends:
* Compile-only dependency for JVM and JS (no runtime dependencies)
* Compile and runtime dependency for Kotlin/Native
* For **JVM**: an atomic value is represented as a plain value atomically updated with `java.util.concurrent.atomic.AtomicXxxFieldUpdater` from the Java standard library.
* For **JS**: an atomic value is represented as a plain value.
* For **Native** and **Wasm**: an atomic value is not transformed, it remains boxed, and `kotlinx-atomicfu` library is used as a runtime dependency.
* Use Kotlin-specific extensions (e.g. inline `loop`, `update`, `updateAndGet` functions).
* Use atomic arrays, user-defined extensions on atomics and locks (see [more features](#more-features)).
* [Tracing operations](#tracing-operations) for debugging.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,15 @@ private fun Project.needsJvmIrTransformation(target: KotlinTarget): Boolean =
rootProject.getBooleanProperty(ENABLE_JVM_IR_TRANSFORMATION) &&
(target.platformType == KotlinPlatformType.jvm || target.platformType == KotlinPlatformType.androidJvm)

private fun KotlinTarget.isJsIrTarget() = (this is KotlinJsTarget && this.irTarget != null) || this is KotlinJsIrTarget
private fun KotlinTarget.isJsIrTarget() =
(this is KotlinJsTarget && this.irTarget != null) ||
(this is KotlinJsIrTarget && this.platformType != KotlinPlatformType.wasm)

private fun Project.isTransformationDisabled(target: KotlinTarget): Boolean {
val platformType = target.platformType
return !config.transformJvm && (platformType == KotlinPlatformType.jvm || platformType == KotlinPlatformType.androidJvm) ||
!config.transformJs && platformType == KotlinPlatformType.js
!config.transformJs && platformType == KotlinPlatformType.js ||
platformType == KotlinPlatformType.wasm
}

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

private fun Project.configureMultiplatformTransformation() =
withKotlinTargets { target ->
if (target.platformType == KotlinPlatformType.common || target.platformType == KotlinPlatformType.native) {
return@withKotlinTargets // skip the common & native targets -- no transformation for them
if (target.platformType == KotlinPlatformType.common ||
target.platformType == KotlinPlatformType.native ||
target.platformType == KotlinPlatformType.wasm
) {
return@withKotlinTargets // skip creation of transformation task for common, native and wasm targets
}
configureTransformationForTarget(target)
}
Expand Down
54 changes: 42 additions & 12 deletions atomicfu/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
*/

import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
import static KotlinVersion.*

apply plugin: 'kotlin-multiplatform'
apply from: rootProject.file("gradle/targets.gradle")
Expand All @@ -20,10 +19,6 @@ ext {
}
}

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

kotlin {
targets {
Expand All @@ -43,6 +38,15 @@ kotlin {
// JVM -- always
jvm()

// Wasm -- always
wasmJs {
nodejs()
}

wasmWasi {
nodejs()
}

sourceSets {
commonMain {
dependencies {
Expand All @@ -55,7 +59,13 @@ kotlin {
implementation 'org.jetbrains.kotlin:kotlin-test-annotations-common'
}
}

jsAndWasmSharedMain {
dependsOn(sourceSets.commonMain)
}

jsMain {
dependsOn(sourceSets.jsAndWasmSharedMain)
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-js'
}
Expand All @@ -65,6 +75,32 @@ kotlin {
implementation 'org.jetbrains.kotlin:kotlin-test-js'
}
}

wasmJsMain {
dependsOn(sourceSets.jsAndWasmSharedMain)
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-wasm-js'
}
}

wasmJsTest {
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-test-wasm-js'
}
}

wasmWasiMain {
dependsOn(sourceSets.jsAndWasmSharedMain)
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib-wasm-wasi'
}
}
wasmWasiTest {
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-test-wasm-wasi'
}
}

jvmMain {
dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib'
Expand Down Expand Up @@ -116,12 +152,6 @@ if (rootProject.ext.native_targets_enabled) {
addTarget(presets.androidNativeX64)
addTarget(presets.mingwX64)
addTarget(presets.watchosDeviceArm64)

// These targets will be removed in 1.9.20, remove them conditionally for the train builds
if (enableDeprecatedNativeTargets) {
addTarget(presets.iosArm32)
addTarget(presets.watchosX86)
}
}
}

Expand Down Expand Up @@ -331,4 +361,4 @@ tasks.clean {
setDelete(layout.buildDirectory.asFileTree.matching {
exclude("tmp/.cache/expanded/expanded.lock")
})
}
}
27 changes: 13 additions & 14 deletions atomicfu/src/commonMain/kotlin/kotlinx/atomicfu/AtomicFU.common.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

package kotlinx.atomicfu

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

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

@JsName(ARRAY_SIZE)
@OptionalJsName(ARRAY_SIZE)
public val size: Int
get() = array.size

@JsName(ARRAY_ELEMENT_GET)
@OptionalJsName(ARRAY_ELEMENT_GET)
public operator fun get(index: Int): AtomicInt = array[index]
}

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

@JsName(ARRAY_SIZE)
@OptionalJsName(ARRAY_SIZE)
public val size: Int
get() = array.size

@JsName(ARRAY_ELEMENT_GET)
@OptionalJsName(ARRAY_ELEMENT_GET)
public operator fun get(index: Int): AtomicLong = array[index]
}

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

@JsName(ARRAY_SIZE)
@OptionalJsName(ARRAY_SIZE)
public val size: Int
get() = array.size

@JsName(ARRAY_ELEMENT_GET)
@OptionalJsName(ARRAY_ELEMENT_GET)
public operator fun get(index: Int): AtomicBoolean = array[index]
}


// ==================================== AtomicArray ====================================

@JsName(ATOMIC_REF_ARRAY)
@OptionalJsName(ATOMIC_REF_ARRAY)
public class AtomicArray<T> internal constructor(size: Int) {
private val array = Array(size) { atomic<T?>(null) }

@JsName(ARRAY_SIZE)
@OptionalJsName(ARRAY_SIZE)
public val size: Int
get() = array.size

@JsName(ARRAY_ELEMENT_GET)
@OptionalJsName(ARRAY_ELEMENT_GET)
public operator fun get(index: Int): AtomicRef<T?> = array[index]
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package kotlinx.atomicfu

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

Expand Down
17 changes: 17 additions & 0 deletions atomicfu/src/commonMain/kotlin/kotlinx/atomicfu/OptionalJsName.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package kotlinx.atomicfu

/**
* This annotation actualized with JsName in JS platform and not actualized in others.
*/
@OptIn(ExperimentalMultiplatform::class)
@OptionalExpectation
@Retention(AnnotationRetention.BINARY)
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
expect annotation class OptionalJsName(val name: String)
11 changes: 5 additions & 6 deletions atomicfu/src/commonMain/kotlin/kotlinx/atomicfu/Trace.common.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

package kotlinx.atomicfu

import kotlin.js.JsName
import kotlin.internal.InlineOnly

/**
Expand Down Expand Up @@ -66,30 +65,30 @@ public expect val traceFormatDefault: TraceFormat
/**
* Base class for implementations of `Trace`.
*/
@JsName(TRACE_BASE_CONSTRUCTOR)
@OptionalJsName(TRACE_BASE_CONSTRUCTOR)
public open class TraceBase internal constructor() {
/**
* Accepts the logging [event] and appends it to the trace.
*/
@JsName(TRACE_APPEND_1)
@OptionalJsName(TRACE_APPEND_1)
public open fun append(event: Any) {}

/**
* Accepts the logging events [event1], [event2] and appends them to the trace.
*/
@JsName(TRACE_APPEND_2)
@OptionalJsName(TRACE_APPEND_2)
public open fun append(event1: Any, event2: Any) {}

/**
* Accepts the logging events [event1], [event2], [event3] and appends them to the trace.
*/
@JsName(TRACE_APPEND_3)
@OptionalJsName(TRACE_APPEND_3)
public open fun append(event1: Any, event2: Any, event3: Any) {}

/**
* Accepts the logging events [event1], [event2], [event3], [event4] and appends them to the trace.
*/
@JsName(TRACE_APPEND_4)
@OptionalJsName(TRACE_APPEND_4)
public open fun append(event1: Any, event2: Any, event3: Any, event4: Any) {}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import kotlin.js.JsName
/**
* Trace string formatter.
*/
@JsName(TRACE_FORMAT_CLASS)
@OptionalJsName(TRACE_FORMAT_CLASS)
public open class TraceFormat {
/**
* Formats trace at the given [index] with the given [event] of Any type.
*/
@JsName(TRACE_FORMAT_FORMAT_FUNCTION)
@OptionalJsName(TRACE_FORMAT_FORMAT_FUNCTION)
public open fun format(index: Int, event: Any): String = "$index: $event"
}

Expand Down
Loading

0 comments on commit 5fe6c0d

Please sign in to comment.