diff --git a/libraries/stdlib/js-ir/src/kotlin/coroutines_13/IntrinsicsJs.kt b/libraries/stdlib/js-ir/src/kotlin/coroutines_13/IntrinsicsJs.kt index 814182687a3e0..cd12dfe09ed44 100644 --- a/libraries/stdlib/js-ir/src/kotlin/coroutines_13/IntrinsicsJs.kt +++ b/libraries/stdlib/js-ir/src/kotlin/coroutines_13/IntrinsicsJs.kt @@ -51,6 +51,12 @@ public actual inline fun (suspend R.() -> T).startCoroutineUninterceptedO return this.asDynamic()(receiver, completion) } +@InlineOnly +internal actual inline fun (suspend R.(P) -> T).startCoroutineUninterceptedOrReturn( + receiver: R, + param: P, + completion: Continuation +): Any? = this.asDynamic()(receiver, param, completion) /** * Creates unintercepted coroutine without receiver and with result type [T]. diff --git a/libraries/stdlib/js-v1/src/kotlin/coroutines/intrinsics/IntrinsicsJs.kt b/libraries/stdlib/js-v1/src/kotlin/coroutines/intrinsics/IntrinsicsJs.kt index 970d9c5019d35..ac3419090b4cb 100644 --- a/libraries/stdlib/js-v1/src/kotlin/coroutines/intrinsics/IntrinsicsJs.kt +++ b/libraries/stdlib/js-v1/src/kotlin/coroutines/intrinsics/IntrinsicsJs.kt @@ -45,6 +45,12 @@ public actual inline fun (suspend R.() -> T).startCoroutineUninterceptedO completion: Continuation ): Any? = this.asDynamic()(receiver, completion, false) +@InlineOnly +internal actual inline fun (suspend R.(P) -> T).startCoroutineUninterceptedOrReturn( + receiver: R, + param: P, + completion: Continuation +): Any? = this.asDynamic()(receiver, param, completion, false) /** * Creates unintercepted coroutine without receiver and with result type [T]. diff --git a/libraries/stdlib/jvm/src/kotlin/coroutines/intrinsics/IntrinsicsJvm.kt b/libraries/stdlib/jvm/src/kotlin/coroutines/intrinsics/IntrinsicsJvm.kt index 11f1a20397802..3e8c432330498 100644 --- a/libraries/stdlib/jvm/src/kotlin/coroutines/intrinsics/IntrinsicsJvm.kt +++ b/libraries/stdlib/jvm/src/kotlin/coroutines/intrinsics/IntrinsicsJvm.kt @@ -50,6 +50,12 @@ public actual inline fun (suspend R.() -> T).startCoroutineUninterceptedO completion: Continuation ): Any? = (this as Function2, Any?>).invoke(receiver, completion) +@InlineOnly +internal actual inline fun (suspend R.(P) -> T).startCoroutineUninterceptedOrReturn( + receiver: R, + param: P, + completion: Continuation +): Any? = (this as Function3, Any?>).invoke(receiver, param, completion) // JVM declarations diff --git a/libraries/stdlib/src/kotlin/coroutines/CoroutinesIntrinsicsH.kt b/libraries/stdlib/src/kotlin/coroutines/CoroutinesIntrinsicsH.kt index 691f355bdd6ea..c779c810e3e1d 100644 --- a/libraries/stdlib/src/kotlin/coroutines/CoroutinesIntrinsicsH.kt +++ b/libraries/stdlib/src/kotlin/coroutines/CoroutinesIntrinsicsH.kt @@ -44,6 +44,13 @@ public expect inline fun (suspend R.() -> T).startCoroutineUninterceptedO completion: Continuation ): Any? +// Internal version that support arity-2 suspending functions, might be made public in the future if needed +internal expect inline fun (suspend R.(P) -> T).startCoroutineUninterceptedOrReturn( + receiver: R, + param: P, + completion: Continuation +): Any? + @SinceKotlin("1.3") public expect fun (suspend () -> T).createCoroutineUnintercepted( completion: Continuation diff --git a/libraries/stdlib/src/kotlin/util/DeepRecursive.kt b/libraries/stdlib/src/kotlin/util/DeepRecursive.kt new file mode 100644 index 0000000000000..1302739c0e829 --- /dev/null +++ b/libraries/stdlib/src/kotlin/util/DeepRecursive.kt @@ -0,0 +1,218 @@ +/* + * 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 kotlin + +import kotlin.coroutines.* +import kotlin.coroutines.intrinsics.* + +/** + * Defines deep recursive function that keeps its stack on the heap, + * which allows very deep recursive computations that do not use the actual call stack. + * To initiate a call to this deep recursive function use its [invoke] function. + * As a rule of thumb, it should be used if recursion goes deeper than a thousand calls. + * + * The [DeepRecursiveFunction] takes one parameter of type [T] and returns a result of type [R]. + * The [block] of code defines the body of a recursive function. In this block + * [callRecursive][DeepRecursiveScope.callRecursive] function can be used to make a recursive call + * to the declared function. Other instances of [DeepRecursiveFunction] can be called + * in this scope with `callRecursive` extension, too. + * + * For example, take a look at the following recursive tree class and a deeply + * recursive instance of this tree with 100K nodes: + * + * ``` + * class Tree(val left: Tree? = null, val right: Tree? = null) + * val deepTree = generateSequence(Tree()) { Tree(it) }.take(100_000).last() + * ``` + * + * A regular recursive function can be defined to compute a depth of a tree: + * + * ``` + * fun depth(t: Tree?): Int = + * if (t == null) 0 else max(depth(t.left), depth(t.right)) + 1 + * println(depth(deepTree)) // StackOverflowError + * ``` + * + * If this `depth` function is called for a `deepTree` it produces [StackOverflowError] because of deep recursion. + * However, the `depth` function can be rewritten using `DeepRecursiveFunction` in the following way, and then + * it successfully computes [`depth(deepTree)`][DeepRecursiveFunction.invoke] expression: + * + * ``` + * val depth = DeepRecursiveFunction { t -> + * if (t == null) 0 else max(callRecursive(t.left), callRecursive(t.right)) + 1 + * } + * println(depth(deepTree)) // Ok + * ``` + * + * Deep recursive functions can also mutually call each other using a heap for the stack via + * [callRecursive][DeepRecursiveScope.callRecursive] extension. For example, the + * following pair of mutually recursive functions computes the number of tree nodes at even depth in the tree. + * + * ``` + * val mutualRecursion = object { + * val even: DeepRecursiveFunction = DeepRecursiveFunction { t -> + * if (t == null) 0 else odd.callRecursive(t.left) + odd.callRecursive(t.right) + 1 + * } + * val odd: DeepRecursiveFunction = DeepRecursiveFunction { t -> + * if (t == null) 0 else even.callRecursive(t.left) + even.callRecursive(t.right) + * } + * } + * ``` + * + * @param [T] the function parameter type. + * @param [R] the function result type. + * @param block the function body. + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public class DeepRecursiveFunction( + internal val block: suspend DeepRecursiveScope.(T) -> R +) + +/** + * Initiates a call to this deep recursive function, forming a root of the call tree. + * + * This operator should not be used from inside of [DeepRecursiveScope] as it uses the call stack slot for + * initial recursive invocation. From inside of [DeepRecursiveScope] use + * [callRecursive][DeepRecursiveScope.callRecursive]. + */ +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public operator fun DeepRecursiveFunction.invoke(value: T): R = + DeepRecursiveScopeImpl(block, value).runCallLoop() + +/** + * A scope class for [DeepRecursiveFunction] function declaration that defines [callRecursive] methods to + * recursively call this function or another [DeepRecursiveFunction] putting the call activation frame on the heap. + * + * @param [T] function parameter type. + * @param [R] function result type. + */ +@RestrictsSuspension +@SinceKotlin("1.4") +@ExperimentalStdlibApi +public sealed class DeepRecursiveScope { + /** + * Makes recursive call to this [DeepRecursiveFunction] function putting the call activation frame on the heap, + * as opposed to the actual call stack that is used by a regular recursive call. + */ + public abstract suspend fun callRecursive(value: T): R + + /** + * Makes call to the specified [DeepRecursiveFunction] function putting the call activation frame on the heap, + * as opposed to the actual call stack that is used by a regular call. + */ + public abstract suspend fun DeepRecursiveFunction.callRecursive(value: U): S + + @Deprecated( + level = DeprecationLevel.ERROR, + message = + "'invoke' should not be called from DeepRecursiveScope. " + + "Use 'callRecursive' to do recursion in the heap instead of the call stack.", + replaceWith = ReplaceWith("this.callRecursive(value)") + ) + @Suppress("UNUSED_PARAMETER") + public operator fun DeepRecursiveFunction<*, *>.invoke(value: Any?): Nothing = + throw UnsupportedOperationException("Should not be called from DeepRecursiveScope") +} + +// ================== Implementation ================== + +@ExperimentalStdlibApi +private typealias DeepRecursiveFunctionBlock = suspend DeepRecursiveScope<*, *>.(Any?) -> Any? + +private val UNDEFINED_RESULT = Result.success(COROUTINE_SUSPENDED) + +@Suppress("UNCHECKED_CAST") +@ExperimentalStdlibApi +private class DeepRecursiveScopeImpl( + block: suspend DeepRecursiveScope.(T) -> R, + value: T +) : DeepRecursiveScope(), Continuation { + // Active function block + private var function: DeepRecursiveFunctionBlock = block as DeepRecursiveFunctionBlock + + // Value to call function with + private var value: Any? = value + + // Continuation of the current call + private var cont: Continuation? = this as Continuation + + // Completion result (completion of the whole call stack) + private var result: Result = UNDEFINED_RESULT + + override val context: CoroutineContext + get() = EmptyCoroutineContext + + override fun resumeWith(result: Result) { + this.cont = null + this.result = result + } + + override suspend fun callRecursive(value: T): R = suspendCoroutineUninterceptedOrReturn { cont -> + // calling the same function that is currently active + this.cont = cont as Continuation + this.value = value + COROUTINE_SUSPENDED + } + + override suspend fun DeepRecursiveFunction.callRecursive(value: U): S = suspendCoroutineUninterceptedOrReturn { cont -> + // calling another recursive function + val function = block as DeepRecursiveFunctionBlock + with(this@DeepRecursiveScopeImpl) { + val currentFunction = this.function + if (function !== currentFunction) { + // calling a different function -- create a trampoline to restore function ref + this.function = function + this.cont = crossFunctionCompletion(currentFunction, cont as Continuation) + } else { + // calling the same function -- direct + this.cont = cont as Continuation + } + this.value = value + } + COROUTINE_SUSPENDED + } + + private fun crossFunctionCompletion( + currentFunction: DeepRecursiveFunctionBlock, + cont: Continuation + ): Continuation = Continuation(EmptyCoroutineContext) { + this.function = currentFunction + // When going back from a trampoline we cannot just call cont.resume (stack usage!) + // We delegate the cont.resumeWith(it) call to runCallLoop + this.cont = cont + this.result = it + } + + @Suppress("UNCHECKED_CAST") + fun runCallLoop(): R { + while (true) { + // Note: cont is set to null in DeepRecursiveScopeImpl.resumeWith when the whole computation completes + val result = this.result + val cont = this.cont + ?: return (result as Result).getOrThrow() // done -- final result + // The order of comparison is important here for that case of rogue class with broken equals + if (UNDEFINED_RESULT == result) { + // call "function" with "value" using "cont" as completion + val r = try { + // This is block.startCoroutine(this, value, cont) + function.startCoroutineUninterceptedOrReturn(this, value, cont) + } catch (e: Throwable) { + cont.resumeWithException(e) + continue + } + // If the function returns without suspension -- calls its continuation immediately + if (r !== COROUTINE_SUSPENDED) + cont.resume(r as R) + } else { + // we returned from a crossFunctionCompletion trampoline -- call resume here + this.result = UNDEFINED_RESULT // reset result back + cont.resumeWith(result) + } + } + } +} diff --git a/libraries/stdlib/test/utils/DeepRecursiveTest.kt b/libraries/stdlib/test/utils/DeepRecursiveTest.kt new file mode 100644 index 0000000000000..1372bdafa2c62 --- /dev/null +++ b/libraries/stdlib/test/utils/DeepRecursiveTest.kt @@ -0,0 +1,143 @@ +/* + * 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 test.utils + +import kotlin.test.* + +class DeepRecursiveTest { + @Test + fun testSimpleReturn() { + // just returns a value without any recursive calls + val ok = DeepRecursiveFunction { i -> "Ok$i" } + assertEquals("Ok42", ok(42)) + } + + @Test + fun testDeepTreeDepth() { + val n = 100_000 + assertEquals(n, depth(deepTree(n))) + } + + @Test + fun testBinaryTreeDepth() { + val k = 15 + assertEquals(k, depth(binaryTree(k))) + } + + private class MutualRec { + val even: DeepRecursiveFunction = DeepRecursiveFunction { t -> + if (t == null) 0 else odd.callRecursive(t.left) + odd.callRecursive(t.right) + 1 + } + + val odd: DeepRecursiveFunction = DeepRecursiveFunction { t -> + if (t == null) 0 else even.callRecursive(t.left) + even.callRecursive(t.right) + } + } + + @Test + fun testDeepTreeOddEvenNodesMutual() { + val n = 100_000 + val dt = deepTree(n) + val rec = MutualRec() + assertEquals(n / 2, rec.even(dt)) + assertEquals(n / 2, rec.odd(dt)) + } + + @Test + fun testBinaryTreeOddEvenNodesMutual() { + val k = 15 + val bt = binaryTree(k) + val rec = MutualRec() + assertEquals(21845, rec.even(bt)) + assertEquals(10922, rec.odd(bt)) + } + + private class MutualAndDirectMixRec { + val b: DeepRecursiveFunction = DeepRecursiveFunction { i -> "b$i" } + + val a: DeepRecursiveFunction = DeepRecursiveFunction { i -> + when (i) { + // mix callRecursive calls to other function and in this context + 0 -> b.callRecursive(1) + callRecursive(2) + aa().callRecursive(3) + else -> "a$i" + } + } + + fun aa() = a + } + + @Test + fun testMutualAndDirectMix() { + // mix of callRecursion on this scope and on other DRF + val rec = MutualAndDirectMixRec() + val s = rec.a.invoke(0) + assertEquals("b1a2a3", s) + } + + private class EqualToAnythingClassRec { + var nullCount = 0 + + val a: DeepRecursiveFunction = DeepRecursiveFunction { t -> + if (t == null) EqualToAnything(nullCount++) else b.callRecursive(t.left) + } + + val b: DeepRecursiveFunction = DeepRecursiveFunction { t -> + if (t == null) EqualToAnything(nullCount++) else a.callRecursive(t.left) + } + } + + @Test + fun testEqualToAnythingClass() { + // Mutually recursive tail calls & broken equals + val rec = EqualToAnythingClassRec() + val result = rec.a.invoke(deepTree(100)) + assertEquals(1, rec.nullCount) + assertEquals(0, result.i) + } + + @Test + fun testBadClass() { + val compute = object { + val a: DeepRecursiveFunction = DeepRecursiveFunction { v -> Bad(v.i + 1) } + val b: DeepRecursiveFunction = DeepRecursiveFunction { v -> + when (v.i) { + 0 -> callRecursive(Bad(1)) + 1 -> Bad(a.callRecursive(Bad(19)).i + callRecursive(Bad(2)).i) + 2 -> Bad(a.callRecursive(Bad(20)).i + 1) + else -> error("Cannot happen") + } + } + } + assertEquals(42, compute.b(Bad(0)).i) + } + + private class Tree(val left: Tree? = null, val right: Tree? = null) + + private fun deepTree(n: Int) = generateSequence(Tree()) { Tree(it) }.take(n).last() + + private fun binaryTree(k: Int): Tree? = + if (k == 0) null else Tree(binaryTree(k - 1), binaryTree(k - 1)) + + private val depth = DeepRecursiveFunction { t -> + if (t == null) 0 else maxOf( + callRecursive(t.left), + callRecursive(t.right) + ) + 1 + } + + // It is equals to any other class + private class EqualToAnything(val i: Int) { + override fun equals(other: Any?): Boolean = true + override fun toString(): String = "OK" + } + + // Throws exception on all object methods + private class Bad(val i: Int) { + override fun equals(other: Any?): Boolean = error("BAD") + override fun hashCode(): Int = error("BAD") + override fun toString(): String = error("BAD") + } +} diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index 0f37a6eac3280..87dc22ae92844 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -1,6 +1,20 @@ public abstract interface annotation class kotlin/BuilderInference : java/lang/annotation/Annotation { } +public final class kotlin/DeepRecursiveFunction { + public fun (Lkotlin/jvm/functions/Function3;)V +} + +public final class kotlin/DeepRecursiveKt { + public static final fun invoke (Lkotlin/DeepRecursiveFunction;Ljava/lang/Object;)Ljava/lang/Object; +} + +public abstract class kotlin/DeepRecursiveScope { + public abstract fun callRecursive (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun callRecursive (Lkotlin/DeepRecursiveFunction;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun invoke (Lkotlin/DeepRecursiveFunction;Ljava/lang/Object;)Ljava/lang/Void; +} + public abstract interface annotation class kotlin/Deprecated : java/lang/annotation/Annotation { public abstract fun level ()Lkotlin/DeprecationLevel; public abstract fun message ()Ljava/lang/String;