Skip to content

Commit

Permalink
Wasm WASI Target implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
igoriakovlev committed Mar 13, 2024
1 parent d4501e7 commit f5579e6
Show file tree
Hide file tree
Showing 25 changed files with 519 additions and 71 deletions.
23 changes: 17 additions & 6 deletions buildSrc/src/main/kotlin/SourceSets.kt
Expand Up @@ -40,16 +40,27 @@ fun KotlinSourceSet.configureDirectoryPaths() {
fun NamedDomainObjectContainer<KotlinSourceSet>.groupSourceSets(
groupName: String,
reverseDependencies: List<String>,
dependencies: List<String>
dependencies: List<String>,
alreadyExist: Boolean = false
) {
fun KotlinSourceSet.configureSourceSet(suffix: String) {
for (dep in dependencies) {
dependsOn(get(dep + suffix))
}
for (revDep in reverseDependencies) {
get(revDep + suffix).dependsOn(this)
}
}

val sourceSetSuffixes = listOf("Main", "Test")
for (suffix in sourceSetSuffixes) {
register(groupName + suffix) {
for (dep in dependencies) {
dependsOn(get(dep + suffix))
if (alreadyExist) {
getByName(groupName + suffix) {
configureSourceSet(suffix)
}
for (revDep in reverseDependencies) {
get(revDep + suffix).dependsOn(this)
} else {
register(groupName + suffix) {
configureSourceSet(suffix)
}
}
}
Expand Down
Expand Up @@ -63,6 +63,18 @@ kotlin {
api("org.jetbrains.kotlinx:atomicfu-wasm-js:${version("atomicfu")}")
}
}
@OptIn(org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl::class)
wasmWasi {
nodejs()
compilations["main"]?.dependencies {
api("org.jetbrains.kotlinx:atomicfu-wasm-wasi:${version("atomicfu")}")
}
compilations.configureEach {
compilerOptions.configure {
optIn.add("kotlin.wasm.internal.InternalWasmApi")
}
}
}
applyDefaultHierarchyTemplate()
sourceSets {
commonTest {
Expand Down Expand Up @@ -101,15 +113,27 @@ kotlin {
api("org.jetbrains.kotlin:kotlin-test-wasm-js:${version("kotlin")}")
}
}
groupSourceSets("jsAndWasmShared", listOf("js", "wasmJs"), listOf("common"))
val wasmWasiMain by getting {
}
val wasmWasiTest by getting {
dependencies {
api("org.jetbrains.kotlin:kotlin-test-wasm-wasi:${version("kotlin")}")
}
}
groupSourceSets("jsAndWasmShared", listOf("wasmWasi"), listOf("common"))
groupSourceSets("jsAndWasmJsShared", listOf("js", "wasmJs"), listOf("jsAndWasmShared"))
groupSourceSets("jsAndWasmShared", listOf("jsAndWasmJsShared"), emptyList(), alreadyExist = true)
}
}

// Disable intermediate sourceSet compilation because we do not need js-wasmJs artifact
// Disable intermediate sourceSet compilation because we do not need js-wasm common artifact
tasks.configureEach {
if (name == "compileJsAndWasmSharedMainKotlinMetadata") {
enabled = false
}
if (name == "compileJsAndWasmJsSharedMainKotlinMetadata") {
enabled = false
}
}

tasks.named("jvmTest", Test::class) {
Expand Down
5 changes: 5 additions & 0 deletions integration-testing/smokeTest/build.gradle
Expand Up @@ -48,6 +48,11 @@ kotlin {
implementation kotlin('test-wasm-js')
}
}
wasmWasiTest {
dependencies {
implementation kotlin('test-wasm-wasi')
}
}
jvmTest {
dependencies {
implementation kotlin('test')
Expand Down
11 changes: 7 additions & 4 deletions kotlinx-coroutines-core/build.gradle.kts
Expand Up @@ -18,10 +18,13 @@ apply(plugin = "pub-conventions")
Configure source sets structure for kotlinx-coroutines-core:
TARGETS SOURCE SETS
------- ----------------------------------------------
wasmJs \----------> jsAndWasmShared --------------------+
js / |
V
------------------------------------------------------------
wasmJs \------> jsAndWasmJsShared ----+
js / |
V
wasmWasi --------------------> jsAndWasmShared ----------+
|
V
jvmCore\ --------> jvm ---------> concurrent -------> common
jdk8 / ^
|
Expand Down
33 changes: 4 additions & 29 deletions kotlinx-coroutines-core/js/src/CoroutineContext.kt
@@ -1,8 +1,10 @@
/*
* Copyright 2016-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.coroutines

import kotlinx.browser.*
import kotlinx.coroutines.internal.*
import kotlin.coroutines.*

private external val navigator: dynamic
private const val UNDEFINED = "undefined"
Expand All @@ -28,30 +30,3 @@ private fun isJsdom() = jsTypeOf(navigator) != UNDEFINED &&
jsTypeOf(navigator.userAgent) != UNDEFINED &&
jsTypeOf(navigator.userAgent.match) != UNDEFINED &&
navigator.userAgent.match("\\bjsdom\\b")

@PublishedApi // Used from kotlinx-coroutines-test via suppress, not part of ABI
internal actual val DefaultDelay: Delay
get() = Dispatchers.Default as Delay

public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
val combined = coroutineContext + context
return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
combined + Dispatchers.Default else combined
}

public actual fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext {
return this + addedContext
}

// No debugging facilities on JS
internal actual inline fun <T> withCoroutineContext(context: CoroutineContext, countOrElement: Any?, block: () -> T): T = block()
internal actual inline fun <T> withContinuationContext(continuation: Continuation<*>, countOrElement: Any?, block: () -> T): T = block()
internal actual fun Continuation<*>.toDebugString(): String = toString()
internal actual val CoroutineContext.coroutineName: String? get() = null // not supported on JS

internal actual class UndispatchedCoroutine<in T> actual constructor(
context: CoroutineContext,
uCont: Continuation<T>
) : ScopeCoroutine<T>(context, uCont) {
override fun afterResume(state: Any?) = uCont.resumeWith(recoverResult(state, uCont))
}
35 changes: 35 additions & 0 deletions kotlinx-coroutines-core/jsAndWasmShared/src/CoroutineContext.kt
@@ -0,0 +1,35 @@
/*
* Copyright 2016-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.coroutines

import kotlinx.coroutines.internal.ScopeCoroutine
import kotlin.coroutines.*

@PublishedApi // Used from kotlinx-coroutines-test via suppress, not part of ABI
internal actual val DefaultDelay: Delay
get() = Dispatchers.Default as Delay

public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
val combined = coroutineContext + context
return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
combined + Dispatchers.Default else combined
}

public actual fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext {
return this + addedContext
}

// No debugging facilities on Wasm and JS
internal actual inline fun <T> withCoroutineContext(context: CoroutineContext, countOrElement: Any?, block: () -> T): T = block()
internal actual inline fun <T> withContinuationContext(continuation: Continuation<*>, countOrElement: Any?, block: () -> T): T = block()
internal actual fun Continuation<*>.toDebugString(): String = toString()
internal actual val CoroutineContext.coroutineName: String? get() = null // not supported on Wasm and JS

internal actual class UndispatchedCoroutine<in T> actual constructor(
context: CoroutineContext,
uCont: Continuation<T>
) : ScopeCoroutine<T>(context, uCont) {
override fun afterResume(state: Any?) = uCont.resumeWith(recoverResult(state, uCont))
}
33 changes: 4 additions & 29 deletions kotlinx-coroutines-core/wasmJs/src/CoroutineContext.kt
@@ -1,8 +1,10 @@
/*
* Copyright 2016-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.coroutines

import kotlinx.coroutines.internal.*
import org.w3c.dom.*
import kotlin.coroutines.*

internal external interface JsProcess : JsAny {
fun nextTick(handler: () -> Unit)
Expand All @@ -18,30 +20,3 @@ internal actual fun createDefaultDispatcher(): CoroutineDispatcher =
tryGetProcess()?.let(::NodeDispatcher)
?: tryGetWindow()?.let(::WindowDispatcher)
?: SetTimeoutDispatcher

@PublishedApi // Used from kotlinx-coroutines-test via suppress, not part of ABI
internal actual val DefaultDelay: Delay
get() = Dispatchers.Default as Delay

public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
val combined = coroutineContext + context
return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
combined + Dispatchers.Default else combined
}

public actual fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext {
return this + addedContext
}

// No debugging facilities on Wasm
internal actual inline fun <T> withCoroutineContext(context: CoroutineContext, countOrElement: Any?, block: () -> T): T = block()
internal actual inline fun <T> withContinuationContext(continuation: Continuation<*>, countOrElement: Any?, block: () -> T): T = block()
internal actual fun Continuation<*>.toDebugString(): String = toString()
internal actual val CoroutineContext.coroutineName: String? get() = null // not supported on Wasm

internal actual class UndispatchedCoroutine<in T> actual constructor(
context: CoroutineContext,
uCont: Continuation<T>
) : ScopeCoroutine<T>(context, uCont) {
override fun afterResume(state: Any?) = uCont.resumeWith(recoverResult(state, uCont))
}
7 changes: 7 additions & 0 deletions kotlinx-coroutines-core/wasmWasi/src/CoroutineContext.kt
@@ -0,0 +1,7 @@
/*
* Copyright 2016-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.coroutines

internal actual fun createDefaultDispatcher(): CoroutineDispatcher = WasiDispatcher
14 changes: 14 additions & 0 deletions kotlinx-coroutines-core/wasmWasi/src/Debug.kt
@@ -0,0 +1,14 @@
/*
* Copyright 2016-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.coroutines

internal actual val DEBUG: Boolean = false

internal actual val Any.hexAddress: String
get() = this.hashCode().toString()

internal actual val Any.classSimpleName: String get() = this::class.simpleName ?: "Unknown"

internal actual inline fun assert(value: () -> Boolean) {}
11 changes: 11 additions & 0 deletions kotlinx-coroutines-core/wasmWasi/src/EventLoopException.kt
@@ -0,0 +1,11 @@
/*
* Copyright 2016-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.coroutines

/**
* Thrown when multiply exception were thrown in event loop.
* @see runEventLoop
*/
public class EventLoopException(public val causes: List<Throwable>) : Throwable("Multiple exceptions were thrown in the event loop.")
31 changes: 31 additions & 0 deletions kotlinx-coroutines-core/wasmWasi/src/WasiDispatcher.kt
@@ -0,0 +1,31 @@
/*
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.coroutines

import kotlinx.coroutines.internal.*
import kotlin.coroutines.*

internal object WasiDispatcher: CoroutineDispatcher(), Delay {
override fun limitedParallelism(parallelism: Int): CoroutineDispatcher {
parallelism.checkParallelism()
return this
}

override fun dispatch(context: CoroutineContext, block: Runnable) {
registerEvent(0) { block.run() }
}

override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
val event = registerEvent(delayToNanos(timeMillis)) { block.run() }
return DisposableHandle { event.cancel() }
}

override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
val event = registerEvent(delayToNanos(timeMillis)) {
with(continuation) { resumeUndispatched(Unit) }
}
continuation.invokeOnCancellation(handler = { event.cancel() })
}
}
@@ -0,0 +1,9 @@
/*
* Copyright 2016-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.coroutines.internal

internal actual fun propagateExceptionFinalResort(exception: Throwable) {
println(exception.toString())
}
17 changes: 17 additions & 0 deletions kotlinx-coroutines-core/wasmWasi/src/internal/CoroutineRunner.kt
@@ -0,0 +1,17 @@
/*
* Copyright 2016-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.coroutines.internal

import kotlinx.coroutines.*
import kotlin.coroutines.*

@InternalCoroutinesApi
public fun runTestCoroutine(context: CoroutineContext, block: suspend CoroutineScope.() -> Unit) {
val newContext = GlobalScope.newCoroutineContext(context)
val coroutine = object: AbstractCoroutine<Unit>(newContext, true, true) {}
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
runEventLoop()
if (coroutine.isCancelled) throw coroutine.getCancellationException().let { it.cause ?: it }
}

0 comments on commit f5579e6

Please sign in to comment.