JS: simplify how suspend functions inlined

This commit is contained in:
Denis Zharkov
2017-01-13 19:37:29 +03:00
parent 2fcc2bbae0
commit c479aa7d65
14 changed files with 92 additions and 156 deletions
@@ -1,3 +1,4 @@
// IGNORE_BACKEND: JS
// WITH_RUNTIME
// WITH_COROUTINES
import kotlin.coroutines.*
@@ -73,18 +73,6 @@ var HasMetadata.sideEffects: SideEffectKind by MetadataProperty(default = SideEf
*/
var JsInvocation.isSuspend: Boolean by MetadataProperty(default = false)
/**
* Denotes a pre-suspend call-site that is to be processed by coroutine transformer.
* For normal suspend call-sites both [isSuspend] and [isPreSuspend] present.
* For inlined suspend calls fake calls are generated before and after inlined function body.
*/
var JsInvocation.isPreSuspend: Boolean by MetadataProperty(default = false)
/**
* Denotes a fake suspend call for inlining purposes.
*/
var JsInvocation.isFakeSuspend: Boolean by MetadataProperty(default = false)
/**
* Denotes a reference to coroutine's `result` field that contains result of
* last suspended invocation.
+1
View File
@@ -14,5 +14,6 @@
<orderEntry type="module" module-name="js.parser" />
<orderEntry type="module" module-name="frontend" />
<orderEntry type="module" module-name="util" />
<orderEntry type="module" module-name="backend-common" />
</component>
</module>
@@ -17,7 +17,10 @@
package org.jetbrains.kotlin.js.coroutine
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.*
import org.jetbrains.kotlin.js.backend.ast.metadata.SideEffectKind
import org.jetbrains.kotlin.js.backend.ast.metadata.isSuspend
import org.jetbrains.kotlin.js.backend.ast.metadata.sideEffects
import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic
import org.jetbrains.kotlin.js.inline.util.collectBreakContinueTargets
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.utils.DFS
@@ -35,7 +38,6 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
private lateinit var nodesToSplit: Set<JsNode>
private var currentCatchBlock = globalCatchBlock
private val tryStack = mutableListOf(TryBlock(globalCatchBlock, null))
private var suspendTarget: CoroutineBlock? = null
var hasFinallyBlocks = false
get
@@ -331,45 +333,39 @@ class CoroutineBodyTransformer(private val program: JsProgram, private val conte
private fun handleExpression(expression: JsExpression): JsExpression? {
return if (expression is JsInvocation) {
var result: JsExpression? = expression
if (expression.isPreSuspend) {
result = handlePreSuspend(expression)
}
if (expression.isSuspend) {
handleSuspend(expression)
result = null
}
result
if (handleInvocation(expression)) null else expression
}
else {
val assignment = JsAstUtils.decomposeAssignment(expression)
if (assignment != null) {
(assignment.second as? JsInvocation)?.let { return if (handleInvocation(it)) null else expression }
}
expression
}
}
private fun handlePreSuspend(invocation: JsInvocation): JsExpression? {
val nextBlock = CoroutineBlock()
currentStatements += state(nextBlock)
suspendTarget = nextBlock
return if (invocation.isFakeSuspend) null else invocation
private fun handleInvocation(expression: JsInvocation): Boolean {
return if (expression.isSuspend) {
handleSuspend(expression)
true
}
else {
false
}
}
private fun handleSuspend(invocation: JsInvocation) {
val invokeExpression = if (invocation.isFakeSuspend) invocation.arguments.getOrNull(0) else invocation
val statements = if (invokeExpression == null) {
listOf(JsReturn(context.metadata.suspendObjectRef.deepCopy()))
val nextBlock = CoroutineBlock()
currentStatements += state(nextBlock)
val resultRef = JsNameRef(context.metadata.resultName, JsAstUtils.stateMachineReceiver()).apply {
sideEffects = SideEffectKind.DEPENDS_ON_STATE
}
else {
val resultRef = JsNameRef(context.metadata.resultName, JsAstUtils.stateMachineReceiver()).apply {
sideEffects = SideEffectKind.DEPENDS_ON_STATE
}
val invocationStatement = JsAstUtils.assignment(resultRef, invokeExpression).makeStmt()
val suspendCondition = JsAstUtils.equality(resultRef.deepCopy(), context.metadata.suspendObjectRef.deepCopy())
val suspendIfNeeded = JsIf(suspendCondition, JsReturn(context.metadata.suspendObjectRef.deepCopy()))
listOf(invocationStatement, suspendIfNeeded, JsBreak())
}
currentStatements += statements
currentBlock = suspendTarget!!
val invocationStatement = JsAstUtils.assignment(resultRef, invocation).makeStmt()
val suspendCondition = JsAstUtils.equality(resultRef.deepCopy(), context.metadata.suspendObjectRef.deepCopy())
val suspendIfNeeded = JsIf(suspendCondition, JsReturn(context.metadata.suspendObjectRef.deepCopy()))
currentStatements += listOf(invocationStatement, suspendIfNeeded, JsBreak())
currentBlock = nextBlock
}
private fun state(target: CoroutineBlock): List<JsStatement> {
@@ -17,14 +17,13 @@
package org.jetbrains.kotlin.js.inline
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.*
import org.jetbrains.kotlin.js.backend.ast.metadata.isSuspend
import org.jetbrains.kotlin.js.backend.ast.metadata.staticRef
import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic
import org.jetbrains.kotlin.js.inline.clean.removeDefaultInitializers
import org.jetbrains.kotlin.js.inline.clean.removeFakeSuspend
import org.jetbrains.kotlin.js.inline.context.InliningContext
import org.jetbrains.kotlin.js.inline.context.NamingContext
import org.jetbrains.kotlin.js.inline.util.*
import org.jetbrains.kotlin.js.inline.util.rewriters.ReturnReplacingVisitor
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
class FunctionInlineMutator
private constructor(
@@ -32,7 +31,7 @@ private constructor(
private val inliningContext: InliningContext
) {
private val invokedFunction: JsFunction
private val namingContext: NamingContext
private val namingContext = inliningContext.newNamingContext()
private val body: JsBlock
private var resultExpr: JsExpression? = null
private var resultName: JsName? = null
@@ -40,23 +39,12 @@ private constructor(
private val currentStatement = inliningContext.statementContext.currentNode
init {
namingContext = inliningContext.newNamingContext()
val functionContext = inliningContext.functionContext
invokedFunction = uncoverClosure(functionContext.getFunctionDefinition(call).deepCopy())
// Removing fakeSuspend is not just an optimization.
// Reentrant suspends are not supported by coroutine transformers.
body = if (call.isSuspend) invokedFunction.body.removeFakeSuspend() else invokedFunction.body
body = invokedFunction.body
}
private fun process() {
if (call.isSuspend) {
val fakeSuspendCall = JsInvocation(JsAstUtils.pureFqn("fakeSuspend", JsAstUtils.pureFqn("Kotlin", null)))
fakeSuspendCall.isPreSuspend = true
fakeSuspendCall.isFakeSuspend = true
body.statements.add(0, JsAstUtils.asSyntheticStatement(fakeSuspendCall))
}
val arguments = getArguments()
val parameters = getParameters()
@@ -125,10 +113,6 @@ private constructor(
val visitor = ReturnReplacingVisitor(resultExpr as? JsNameRef, breakName.makeRef(), invokedFunction, call.isSuspend)
visitor.accept(body)
visitor.makeFakeSuspendCall(null)?.let { fakeSuspend ->
body.statements += JsAstUtils.asSyntheticStatement(fakeSuspend)
}
}
private fun getResultReference(): JsNameRef? {
@@ -20,7 +20,10 @@ import com.intellij.psi.PsiElement;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.backend.common.CommonCoroutineCodegenUtilKt;
import org.jetbrains.kotlin.descriptors.CallableDescriptor;
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
import org.jetbrains.kotlin.descriptors.FunctionDescriptor;
import org.jetbrains.kotlin.diagnostics.DiagnosticSink;
import org.jetbrains.kotlin.diagnostics.Errors;
import org.jetbrains.kotlin.js.backend.ast.*;
@@ -180,6 +183,12 @@ public class JsInliner extends JsVisitorWithContextImpl {
}
private void inline(@NotNull JsInvocation call, @NotNull JsContext context) {
DeclarationDescriptor callDescriptor = MetadataProperties.getDescriptor(call);
if (isSuspendWithCurrentContinuation(callDescriptor)) {
inlineSuspendWithCurrentContinuation(call, context);
return;
}
JsInliningContext inliningContext = getInliningContext();
InlineableResult inlineableResult = getInlineableCallReplacement(call, inliningContext);
@@ -205,6 +214,22 @@ public class JsInliner extends JsVisitorWithContextImpl {
context.replaceMe(resultExpression);
}
private static boolean isSuspendWithCurrentContinuation(@Nullable DeclarationDescriptor descriptor) {
if (!(descriptor instanceof FunctionDescriptor)) return false;
return CommonCoroutineCodegenUtilKt.isBuiltInSuspendCoroutineOrReturn((FunctionDescriptor) descriptor.getOriginal());
}
private void inlineSuspendWithCurrentContinuation(@NotNull JsInvocation call, @NotNull JsContext context) {
JsInliningContext inliningContext = getInliningContext();
JsFunction containingFunction = inliningContext.function;
JsExpression lambda = call.getArguments().get(0);
JsParameter continuationParam = containingFunction.getParameters().get(containingFunction.getParameters().size() - 1);
JsInvocation invocation = new JsInvocation(lambda, continuationParam.getName().makeRef());
MetadataProperties.setSuspend(invocation, true);
context.replaceMe(accept(invocation));
}
@NotNull
private JsInliningContext getInliningContext() {
return inliningContexts.peek();
@@ -250,7 +275,11 @@ public class JsInliner extends JsVisitorWithContextImpl {
private class JsInliningContext implements InliningContext {
private final FunctionContext functionContext;
JsInliningContext(JsFunction function) {
@NotNull
public final JsFunction function;
JsInliningContext(@NotNull JsFunction function) {
this.function = function;
functionContext = new FunctionContext(function, functionReader) {
@Nullable
@Override
@@ -563,7 +563,7 @@ internal class TemporaryVariableElimination(private val function: JsFunction) {
val name = x.name
if (name != null && x.qualifier == null && name in namesToSubstitute) {
val replacement = accept(definedValues[name]!!)
ctx.replaceMe(replacement.apply { synthetic = true })
ctx.replaceMe(replacement.deepCopy().apply { synthetic = true })
return false
}
return super.visit(x, ctx)
@@ -1,48 +0,0 @@
/*
* Copyright 2010-2016 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.inline.clean
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.isFakeSuspend
import org.jetbrains.kotlin.js.backend.ast.metadata.isPreSuspend
import org.jetbrains.kotlin.js.backend.ast.metadata.isSuspend
import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic
import org.jetbrains.kotlin.js.translate.context.Namer
fun <T : JsNode> T.removeFakeSuspend(): T {
val visitor = object : JsVisitorWithContextImpl() {
override fun endVisit(x: JsInvocation, ctx: JsContext<in JsNode>) {
if (x.isFakeSuspend) {
ctx.replaceMe(x.arguments.getOrElse(0) { Namer.getUndefinedExpression() })
}
else {
x.isSuspend = false
x.isPreSuspend = false
}
super.endVisit(x, ctx)
}
override fun visit(x: JsExpressionStatement, ctx: JsContext<*>): Boolean {
val expression = x.expression
if (expression is JsInvocation && expression.isFakeSuspend && expression.arguments.isEmpty()) {
x.synthetic = true
}
return super.visit(x, ctx)
}
}
return visitor.accept(this)
}
@@ -34,7 +34,7 @@ fun aliasArgumentsIfNeeded(
val replacement = context.getFreshName(paramName).apply {
staticRef = arg
context.newVar(this, arg)
context.newVar(this, arg.deepCopy())
}.makeRef()
context.replaceName(paramName, replacement)
@@ -21,10 +21,8 @@ import org.jetbrains.kotlin.js.backend.ast.*
class NameReplacingVisitor(private val replaceMap: Map<JsName, JsExpression>) : JsVisitorWithContextImpl() {
override fun endVisit(x: JsNameRef, ctx: JsContext<JsNode>) {
val replacement = replaceMap[x.name]
if (replacement == null) return
ctx.replaceMe(replacement)
val replacement = replaceMap[x.name] ?: return
ctx.replaceMe(replacement.deepCopy())
}
override fun endVisit(x: JsVars.JsVar, ctx: JsContext<JsNode>) {
@@ -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.translate.context.Namer
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
class ReturnReplacingVisitor(
@@ -55,25 +56,19 @@ class ReturnReplacingVisitor(
private fun getReturnReplacement(returnExpression: JsExpression?): JsExpression? {
return if (returnExpression != null) {
val assignment = resultRef?.let { lhs ->
val rhs = makeFakeSuspendCall(returnExpression)!!
val rhs = processCoroutineResult(returnExpression)!!
JsAstUtils.assignment(lhs, rhs).apply { synthetic = true }
}
assignment ?: makeFakeSuspendCall(returnExpression)
assignment ?: processCoroutineResult(returnExpression)
}
else {
makeFakeSuspendCall(null)
processCoroutineResult(null)
}
}
fun makeFakeSuspendCall(expression: JsExpression?): JsExpression? {
fun processCoroutineResult(expression: JsExpression?): JsExpression? {
if (!isSuspend) return expression
val fakeSuspendCall = JsInvocation(JsAstUtils.pureFqn("fakeSuspend", JsAstUtils.pureFqn("Kotlin", null)))
fakeSuspendCall.isSuspend = true
fakeSuspendCall.isFakeSuspend = true
if (expression != null) {
fakeSuspendCall.arguments += expression
}
return fakeSuspendCall
val lhs = JsNameRef("\$\$coroutineResult\$\$", JsAstUtils.stateMachineReceiver()).apply { coroutineResult = true }
return JsAstUtils.assignment(lhs, expression ?: Namer.getUndefinedExpression())
}
}
@@ -5795,7 +5795,13 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
@TestMetadata("dispatchResume.kt")
public void testDispatchResume() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/dispatchResume.kt");
doTest(fileName);
try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
}
@TestMetadata("handleException.kt")
@@ -16,18 +16,21 @@
package org.jetbrains.kotlin.js.translate.callTranslator
import org.jetbrains.kotlin.backend.common.isBuiltInSuspendCoroutineOrReturn
import org.jetbrains.kotlin.builtins.isFunctionTypeOrSubtype
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.*
import org.jetbrains.kotlin.js.backend.ast.JsExpression
import org.jetbrains.kotlin.js.backend.ast.JsInvocation
import org.jetbrains.kotlin.js.backend.ast.JsNameRef
import org.jetbrains.kotlin.js.backend.ast.metadata.SideEffectKind
import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineResult
import org.jetbrains.kotlin.js.backend.ast.metadata.isSuspend
import org.jetbrains.kotlin.js.backend.ast.metadata.sideEffects
import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.general.Translation
import org.jetbrains.kotlin.js.translate.reference.CallArgumentTranslator
import org.jetbrains.kotlin.js.translate.reference.CallExpressionTranslator
import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator
import org.jetbrains.kotlin.js.translate.utils.*
import org.jetbrains.kotlin.psi.Call.CallType
import org.jetbrains.kotlin.psi.KtExpression
@@ -35,7 +38,6 @@ import org.jetbrains.kotlin.resolve.calls.callResolverUtil.isInvokeCallOnVariabl
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind.*
import org.jetbrains.kotlin.resolve.inline.InlineStrategy
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
@@ -128,11 +130,6 @@ private fun translateFunctionCall(
inlineResolvedCall: ResolvedCall<out CallableDescriptor>,
explicitReceivers: ExplicitReceivers
): JsExpression {
val descriptorToCall = resolvedCall.resultingDescriptor
if (descriptorToCall is FunctionDescriptor && descriptorToCall.original.isBuiltInSuspendCoroutineOrReturn()) {
return translateCallWithContinuation(context, resolvedCall)
}
val callExpression = context.getCallInfo(resolvedCall, explicitReceivers).translateFunctionCall()
if (CallExpressionTranslator.shouldBeInlined(inlineResolvedCall.resultingDescriptor, context)) {
@@ -141,10 +138,7 @@ private fun translateFunctionCall(
}
if (resolvedCall.resultingDescriptor.isSuspend && context.isInStateMachine) {
context.currentBlock.statements += JsAstUtils.asSyntheticStatement((callExpression as JsInvocation).apply {
isSuspend = true
isPreSuspend = true
})
context.currentBlock.statements += JsAstUtils.asSyntheticStatement((callExpression as JsInvocation).apply { isSuspend = true })
val coroutineRef = TranslationUtils.translateContinuationArgument(context, resolvedCall)
return context.defineTemporary(JsNameRef("\$\$coroutineResult\$\$", coroutineRef).apply {
sideEffects = SideEffectKind.DEPENDS_ON_STATE
@@ -157,15 +151,6 @@ private fun translateFunctionCall(
private val TranslationContext.isInStateMachine
get() = (declarationDescriptor as? FunctionDescriptor)?.requiresStateMachineTransformation(this) == true
private fun translateCallWithContinuation(context: TranslationContext, resolvedCall: ResolvedCall<out FunctionDescriptor>): JsExpression {
val arguments = CallArgumentTranslator.translate(resolvedCall, null, context)
val coroutineArgument = TranslationUtils.getEnclosingContinuationParameter(context)
val invocation = JsInvocation(arguments.valueArguments[0], ReferenceTranslator.translateAsValueReference(coroutineArgument, context))
invocation.inlineStrategy = InlineStrategy.IN_PLACE
context.currentBlock.statements += JsReturn(invocation)
return JsLiteral.NULL
}
fun computeExplicitReceiversForInvoke(
context: TranslationContext,
resolvedCall: ResolvedCall<out FunctionDescriptor>,
@@ -75,7 +75,8 @@ public final class CallExpressionTranslator extends AbstractCallExpressionTransl
if (descriptor instanceof ValueParameterDescriptor) {
return InlineUtil.isInline(descriptor.getContainingDeclaration()) &&
InlineUtil.isInlineLambdaParameter((ParameterDescriptor) descriptor);
InlineUtil.isInlineLambdaParameter((ParameterDescriptor) descriptor) &&
!((ValueParameterDescriptor) descriptor).isCrossinline();
}
return false;