JS: replace suspend inline metadata after inlining
This fixes some issues on coroutine inlining, see tests
This commit is contained in:
@@ -115,6 +115,8 @@ var JsNameRef.coroutineReceiver by MetadataProperty(default = false)
|
||||
|
||||
var JsFunction.forceStateMachine by MetadataProperty(default = false)
|
||||
|
||||
var JsFunction.isInlineableCoroutineBody by MetadataProperty(default = false)
|
||||
|
||||
var JsName.imported by MetadataProperty(default = false)
|
||||
|
||||
var JsFunction.coroutineMetadata: CoroutineMetadata? by MetadataProperty(default = null)
|
||||
@@ -156,7 +158,8 @@ enum class SpecialFunction(val suggestedName: String) {
|
||||
SUSPEND_CALL("suspendCall"),
|
||||
COROUTINE_RESULT("coroutineResult"),
|
||||
COROUTINE_CONTROLLER("coroutineController"),
|
||||
COROUTINE_RECEIVER("coroutineReceiver")
|
||||
COROUTINE_RECEIVER("coroutineReceiver"),
|
||||
SET_COROUTINE_RESULT("setCoroutineResult")
|
||||
}
|
||||
|
||||
enum class BoxingKind {
|
||||
|
||||
@@ -18,6 +18,8 @@ package org.jetbrains.kotlin.js.coroutine
|
||||
|
||||
import org.jetbrains.kotlin.js.backend.ast.*
|
||||
import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata
|
||||
import org.jetbrains.kotlin.js.backend.ast.metadata.isInlineableCoroutineBody
|
||||
import org.jetbrains.kotlin.js.translate.declaration.transformCoroutineMetadataToSpecialFunctions
|
||||
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||
|
||||
@@ -57,6 +59,14 @@ class CoroutineTransformer : JsVisitorWithContextImpl() {
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsFunction, ctx: JsContext<*>): Boolean {
|
||||
if (x.isInlineableCoroutineBody) {
|
||||
x.body = transformCoroutineMetadataToSpecialFunctions(x.body)
|
||||
return false
|
||||
}
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsVars.JsVar, ctx: JsContext<*>): Boolean {
|
||||
val initExpression = x.initExpression
|
||||
if (initExpression != null) {
|
||||
|
||||
@@ -63,7 +63,9 @@ internal class ExpressionDecomposer private constructor(
|
||||
val decomposer = with (statement) {
|
||||
val extractable = match(canBeExtractedByInliner)
|
||||
val containsExtractable = withParentsOfNodes(extractable)
|
||||
val nodesWithSideEffect = match { it !is JsLiteral.JsValueLiteral }
|
||||
val nodesWithSideEffect = match {
|
||||
!(it is JsLiteral.JsValueLiteral || (it is JsExpression && it.sideEffects == SideEffectKind.PURE))
|
||||
}
|
||||
val containsNodeWithSideEffect = withParentsOfNodes(nodesWithSideEffect)
|
||||
|
||||
ExpressionDecomposer(containsExtractable, containsNodeWithSideEffect)
|
||||
|
||||
@@ -233,7 +233,8 @@ class FunctionReader(
|
||||
}
|
||||
|
||||
val position = info.offsetToSourceMapping[offset]
|
||||
val functionExpr = parseFunction(source, info.filePath, position, offset, ThrowExceptionOnErrorReporter, JsRootScope(JsProgram())) ?:
|
||||
val jsScope = JsRootScope(JsProgram())
|
||||
val functionExpr = parseFunction(source, info.filePath, position, offset, ThrowExceptionOnErrorReporter, jsScope) ?:
|
||||
return null
|
||||
functionExpr.fixForwardNameReferences()
|
||||
val (function, wrapper) = if (isWrapped) {
|
||||
@@ -261,12 +262,7 @@ class FunctionReader(
|
||||
wrapperStatements?.forEach { replaceExternalNames(it, replacements, allDefinedNames) }
|
||||
function.markInlineArguments(descriptor)
|
||||
markDefaultParams(function)
|
||||
|
||||
for (externalName in (collectReferencedNames(function) - allDefinedNames)) {
|
||||
info.specialFunctions[externalName.ident]?.let {
|
||||
externalName.specialFunction = it
|
||||
}
|
||||
}
|
||||
markSpecialFunctions(function, allDefinedNames, info, jsScope)
|
||||
|
||||
val namesWithoutSizeEffects = wrapperStatements.orEmpty().asSequence()
|
||||
.flatMap { collectDefinedNames(it).asSequence() }
|
||||
@@ -289,6 +285,33 @@ class FunctionReader(
|
||||
return FunctionWithWrapper(function, wrapper)
|
||||
}
|
||||
|
||||
private fun markSpecialFunctions(function: JsFunction, allDefinedNames: Set<JsName>, info: ModuleInfo, scope: JsScope) {
|
||||
for (externalName in (collectReferencedNames(function) - allDefinedNames)) {
|
||||
info.specialFunctions[externalName.ident]?.let {
|
||||
externalName.specialFunction = it
|
||||
}
|
||||
}
|
||||
|
||||
function.body.accept(object : RecursiveJsVisitor() {
|
||||
override fun visitNameRef(nameRef: JsNameRef) {
|
||||
super.visitNameRef(nameRef)
|
||||
markQualifiedSpecialFunction(nameRef)
|
||||
}
|
||||
|
||||
private fun markQualifiedSpecialFunction(nameRef: JsNameRef) {
|
||||
val qualifier = nameRef.qualifier as? JsNameRef ?: return
|
||||
if (qualifier.ident != info.kotlinVariable || qualifier.qualifier != null) return
|
||||
if (nameRef.name?.specialFunction != null) return
|
||||
|
||||
val specialFunction = specialFunctionsByName[nameRef.ident] ?: return
|
||||
if (nameRef.name == null) {
|
||||
nameRef.name = scope.declareName(nameRef.ident)
|
||||
}
|
||||
nameRef.name!!.specialFunction = specialFunction
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun markDefaultParams(function: JsFunction) {
|
||||
val paramsByNames = function.parameters.associate { it.name to it }
|
||||
for (ifStatement in function.body.statements) {
|
||||
|
||||
+2
-5
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.js.inline.util.rewriters
|
||||
|
||||
import org.jetbrains.kotlin.js.backend.ast.*
|
||||
import org.jetbrains.kotlin.js.backend.ast.metadata.*
|
||||
import org.jetbrains.kotlin.js.coroutine.isStateMachineResult
|
||||
import org.jetbrains.kotlin.js.translate.context.Namer
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||
|
||||
@@ -43,12 +44,8 @@ class ReturnReplacingVisitor(
|
||||
|
||||
ctx.removeMe()
|
||||
|
||||
val returnExpression = x.expression
|
||||
val returnReplacement = getReturnReplacement(x.expression)
|
||||
if (returnReplacement != null) {
|
||||
if (returnExpression != null && returnExpression.isTailCallSuspend) {
|
||||
returnReplacement.isSuspend = true
|
||||
}
|
||||
if (returnReplacement.source == null) {
|
||||
returnReplacement.source = x.source
|
||||
}
|
||||
@@ -74,7 +71,7 @@ class ReturnReplacingVisitor(
|
||||
}
|
||||
|
||||
private fun processCoroutineResult(expression: JsExpression?): JsExpression? {
|
||||
if (!isSuspend) return expression
|
||||
if (!isSuspend || expression.isStateMachineResult()) return expression
|
||||
val lhs = JsNameRef("\$\$coroutineResult\$\$", JsAstUtils.stateMachineReceiver()).apply { coroutineResult = true }
|
||||
return JsAstUtils.assignment(lhs, expression ?: Namer.getUndefinedExpression())
|
||||
}
|
||||
|
||||
@@ -75,6 +75,9 @@ Kotlin.coroutineController = function(qualifier) {
|
||||
Kotlin.coroutineReceiver = function(qualifier) {
|
||||
};
|
||||
|
||||
Kotlin.setCoroutineResult = function(value, qualifier) {
|
||||
};
|
||||
|
||||
Kotlin.getFunctionById = function(id, defaultValue) {
|
||||
return function() {
|
||||
return defaultValue;
|
||||
|
||||
@@ -440,6 +440,7 @@ enum SpecialFunction {
|
||||
COROUTINE_RESULT = 6;
|
||||
COROUTINE_CONTROLLER = 7;
|
||||
COROUTINE_RECEIVER = 8;
|
||||
SET_COROUTINE_RESULT = 9;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -527,6 +527,7 @@ class JsAstDeserializer(program: JsProgram, private val sourceRoots: Iterable<Fi
|
||||
JsAstProtoBuf.SpecialFunction.COROUTINE_RESULT -> SpecialFunction.COROUTINE_RESULT
|
||||
JsAstProtoBuf.SpecialFunction.COROUTINE_CONTROLLER -> SpecialFunction.COROUTINE_CONTROLLER
|
||||
JsAstProtoBuf.SpecialFunction.COROUTINE_RECEIVER -> SpecialFunction.COROUTINE_RECEIVER
|
||||
JsAstProtoBuf.SpecialFunction.SET_COROUTINE_RESULT -> SpecialFunction.SET_COROUTINE_RESULT
|
||||
}
|
||||
|
||||
private fun <T : JsNode> withLocation(fileId: Int?, location: Location?, action: () -> T): T {
|
||||
|
||||
@@ -175,6 +175,10 @@ public final class JsAstProtoBuf {
|
||||
* <code>COROUTINE_RECEIVER = 8;</code>
|
||||
*/
|
||||
COROUTINE_RECEIVER(7, 8),
|
||||
/**
|
||||
* <code>SET_COROUTINE_RESULT = 9;</code>
|
||||
*/
|
||||
SET_COROUTINE_RESULT(8, 9),
|
||||
;
|
||||
|
||||
/**
|
||||
@@ -209,6 +213,10 @@ public final class JsAstProtoBuf {
|
||||
* <code>COROUTINE_RECEIVER = 8;</code>
|
||||
*/
|
||||
public static final int COROUTINE_RECEIVER_VALUE = 8;
|
||||
/**
|
||||
* <code>SET_COROUTINE_RESULT = 9;</code>
|
||||
*/
|
||||
public static final int SET_COROUTINE_RESULT_VALUE = 9;
|
||||
|
||||
|
||||
public final int getNumber() { return value; }
|
||||
@@ -223,6 +231,7 @@ public final class JsAstProtoBuf {
|
||||
case 6: return COROUTINE_RESULT;
|
||||
case 7: return COROUTINE_CONTROLLER;
|
||||
case 8: return COROUTINE_RECEIVER;
|
||||
case 9: return SET_COROUTINE_RESULT;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,6 +563,7 @@ class JsAstSerializer(private val pathResolver: (File) -> String) {
|
||||
SpecialFunction.COROUTINE_RESULT -> JsAstProtoBuf.SpecialFunction.COROUTINE_RESULT
|
||||
SpecialFunction.COROUTINE_CONTROLLER -> JsAstProtoBuf.SpecialFunction.COROUTINE_CONTROLLER
|
||||
SpecialFunction.COROUTINE_RECEIVER -> JsAstProtoBuf.SpecialFunction.COROUTINE_RECEIVER
|
||||
SpecialFunction.SET_COROUTINE_RESULT -> JsAstProtoBuf.SpecialFunction.SET_COROUTINE_RESULT
|
||||
}
|
||||
|
||||
private fun serialize(name: JsName): Int = nameMap.getOrPut(name) {
|
||||
|
||||
+18
@@ -6083,6 +6083,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("indirectInlineUsedAsNonInline.kt")
|
||||
public void testIndirectInlineUsedAsNonInline() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/indirectInlineUsedAsNonInline.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inlineFunInGenericClass.kt")
|
||||
public void testInlineFunInGenericClass() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/inlineFunInGenericClass.kt");
|
||||
@@ -6685,6 +6691,18 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inlineMultiModuleWithInnerInlining.kt")
|
||||
public void testInlineMultiModuleWithInnerInlining() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineMultiModuleWithInnerInlining.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inlineTailCall.kt")
|
||||
public void testInlineTailCall() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/inlineTailCall.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multiModule/simple.kt");
|
||||
|
||||
+3
-1
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.js.backend.ast.JsExpression
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsFunction
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsName
|
||||
import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata
|
||||
import org.jetbrains.kotlin.js.backend.ast.metadata.isInlineableCoroutineBody
|
||||
import org.jetbrains.kotlin.js.descriptorUtils.shouldBeExported
|
||||
import org.jetbrains.kotlin.js.translate.context.TranslationContext
|
||||
import org.jetbrains.kotlin.js.translate.expression.*
|
||||
@@ -100,9 +101,10 @@ abstract class AbstractDeclarationVisitor : TranslatorVisitor<Unit>() {
|
||||
|
||||
if (descriptor.isSuspend && descriptor.isInline && descriptor.shouldBeExported(context.config) && functionAndContext != null) {
|
||||
val innerContext = functionAndContext.second
|
||||
val inlineFunction = transformCoroutineMetadataToSpecialFunctions(context, functionAndContext.first.deepCopy() as JsFunction)
|
||||
val inlineFunction = functionAndContext.first.deepCopy() as JsFunction
|
||||
inlineFunction.name = null
|
||||
inlineFunction.coroutineMetadata = null
|
||||
inlineFunction.isInlineableCoroutineBody = true
|
||||
val metadata = InlineMetadata.compose(inlineFunction, descriptor, innerContext)
|
||||
val functionWithMetadata = metadata.functionWithMetadata(context, descriptor.source.getPsi())
|
||||
context.addDeclarationStatement(functionWithMetadata.makeStmt())
|
||||
|
||||
+38
-22
@@ -14,31 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.js.translate.declaration
|
||||
|
||||
import org.jetbrains.kotlin.js.backend.ast.*
|
||||
import org.jetbrains.kotlin.js.backend.ast.metadata.*
|
||||
import org.jetbrains.kotlin.js.translate.context.TranslationContext
|
||||
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils
|
||||
import org.jetbrains.kotlin.js.translate.context.Namer
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.assignment
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.pureFqn
|
||||
import org.jetbrains.kotlin.js.translate.utils.name
|
||||
|
||||
fun <T : JsNode> transformCoroutineMetadataToSpecialFunctions(context: TranslationContext, node: T): T {
|
||||
fun <T : JsNode> transformCoroutineMetadataToSpecialFunctions(node: T): T {
|
||||
val visitor = object : JsVisitorWithContextImpl() {
|
||||
override fun endVisit(x: JsNameRef, ctx: JsContext<in JsExpression>) {
|
||||
val specialFunction = when {
|
||||
@@ -49,7 +34,11 @@ fun <T : JsNode> transformCoroutineMetadataToSpecialFunctions(context: Translati
|
||||
}
|
||||
if (specialFunction != null) {
|
||||
val arguments = listOfNotNull(x.qualifier).toTypedArray()
|
||||
ctx.replaceMe(TranslationUtils.invokeSpecialFunction(context, specialFunction, *arguments).source(x.source))
|
||||
ctx.replaceMe(JsInvocation(specialFunction.ref(), *arguments).apply {
|
||||
synthetic = x.synthetic
|
||||
sideEffects = x.sideEffects
|
||||
source = x.source
|
||||
})
|
||||
}
|
||||
else {
|
||||
super.endVisit(x, ctx)
|
||||
@@ -59,13 +48,29 @@ fun <T : JsNode> transformCoroutineMetadataToSpecialFunctions(context: Translati
|
||||
override fun endVisit(x: JsExpression, ctx: JsContext<in JsExpression>) {
|
||||
if (x.isSuspend) {
|
||||
x.isSuspend = false
|
||||
ctx.replaceMe(TranslationUtils.invokeSpecialFunction(context, SpecialFunction.SUSPEND_CALL, x).source(x.source))
|
||||
ctx.replaceMe(JsInvocation(SpecialFunction.SUSPEND_CALL.ref(), x).source(x.source))
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(x: JsBinaryOperation, ctx: JsContext<in JsExpression>): Boolean {
|
||||
val lhs = x.arg1
|
||||
if (lhs is JsNameRef && lhs.coroutineResult) {
|
||||
val arguments = listOf(accept(x.arg2)) + listOfNotNull(lhs.qualifier?.let { accept(it) })
|
||||
ctx.replaceMe(JsInvocation(SpecialFunction.SET_COROUTINE_RESULT.ref(), arguments).apply {
|
||||
synthetic = x.synthetic
|
||||
sideEffects = x.sideEffects
|
||||
source = x.source
|
||||
})
|
||||
return false
|
||||
}
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
}
|
||||
return visitor.accept(node)
|
||||
}
|
||||
|
||||
private fun SpecialFunction.ref() = pureFqn(suggestedName, Namer.kotlinObject())
|
||||
|
||||
fun <T : JsNode> transformSpecialFunctionsToCoroutineMetadata(node: T): T {
|
||||
val visitor = object : JsVisitorWithContextImpl() {
|
||||
override fun endVisit(x: JsInvocation, ctx: JsContext<in JsExpression>) {
|
||||
@@ -91,9 +96,20 @@ fun <T : JsNode> transformSpecialFunctionsToCoroutineMetadata(node: T): T {
|
||||
isSuspend = true
|
||||
}
|
||||
}
|
||||
SpecialFunction.SET_COROUTINE_RESULT -> {
|
||||
val lhs = JsNameRef("\$result\$", x.arguments.getOrNull(1)).apply {
|
||||
coroutineResult = true
|
||||
}
|
||||
assignment(lhs, x.arguments[0])
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
replacement?.let { ctx.replaceMe(it) }
|
||||
replacement?.let {
|
||||
it.source = x.source
|
||||
it.sideEffects = x.sideEffects
|
||||
it.synthetic = x.synthetic
|
||||
ctx.replaceMe(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,26 +454,6 @@ public final class JsAstUtils {
|
||||
return new JsObjectLiteral(Collections.singletonList(new JsPropertyInitializer(label, value)));
|
||||
}
|
||||
|
||||
public static JsExpression replaceRootReference(@NotNull JsNameRef fullQualifier, @NotNull JsExpression newQualifier) {
|
||||
if (fullQualifier.getQualifier() == null) {
|
||||
assert Namer.getRootPackageName().equals(fullQualifier.getIdent()) : "Expected root package, but: " + fullQualifier.getIdent();
|
||||
return newQualifier;
|
||||
}
|
||||
|
||||
fullQualifier = fullQualifier.deepCopy();
|
||||
JsNameRef qualifier = fullQualifier;
|
||||
while (true) {
|
||||
JsExpression parent = qualifier.getQualifier();
|
||||
assert parent instanceof JsNameRef : "unexpected qualifier: " + parent + ", original: " + fullQualifier;
|
||||
if (((JsNameRef) parent).getQualifier() == null) {
|
||||
assert Namer.getRootPackageName().equals(((JsNameRef) parent).getIdent());
|
||||
qualifier.setQualifier(newQualifier);
|
||||
return fullQualifier;
|
||||
}
|
||||
qualifier = (JsNameRef) parent;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<JsStatement> flattenStatement(@NotNull JsStatement statement) {
|
||||
if (statement instanceof JsBlock) {
|
||||
@@ -527,7 +507,7 @@ public final class JsAstUtils {
|
||||
|
||||
@NotNull
|
||||
public static JsExpression stateMachineReceiver() {
|
||||
JsNameRef result = new JsNameRef("$this$");
|
||||
JsNameRef result = pureFqn("$this$", null);
|
||||
MetadataProperties.setCoroutineReceiver(result, true);
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user