JS: improve inline suspend function declaration splitter

This commit is contained in:
Anton Bannykh
2019-02-12 14:11:10 +03:00
parent 4b39e2df12
commit 9ad1d8d054
3 changed files with 31 additions and 47 deletions
@@ -73,7 +73,6 @@ fun transformCoroutines(fragments: Iterable<JsProgramFragment>) {
val coroutineTransformer = CoroutineTransformer() val coroutineTransformer = CoroutineTransformer()
for (fragment in fragments) { for (fragment in fragments) {
ImportInfoFragmentInliningScope.process(fragment) { scope -> ImportInfoFragmentInliningScope.process(fragment) { scope ->
InlineSuspendFunctionSplitter(scope).accept(scope.allCode)
coroutineTransformer.accept(scope.allCode) coroutineTransformer.accept(scope.allCode)
} }
} }
@@ -8,64 +8,45 @@ package org.jetbrains.kotlin.js.inline
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata
import org.jetbrains.kotlin.js.backend.ast.metadata.isInlineableCoroutineBody import org.jetbrains.kotlin.js.backend.ast.metadata.isInlineableCoroutineBody
import org.jetbrains.kotlin.js.inline.util.FunctionWithWrapper
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata import org.jetbrains.kotlin.js.translate.expression.InlineMetadata
// Goes through `suspend inline` function definitions and transforms into two entites: // Goes through `suspend inline` function definitions and transforms into two entities:
// 1. A `suspend` function definition, which could be invoked at run time // 1. A `suspend` function definition, which could be invoked at run time
// 2. A `suspend inline` function definition, which doesn't work at run time, but is understood by the inliner. // 2. A `suspend inline` function definition, which doesn't work at run time, but is understood by the inliner.
// TODO remove the wrapped version for `private inline suspend fun`'s
class InlineSuspendFunctionSplitter( class InlineSuspendFunctionSplitter(
val scope: ImportInfoFragmentInliningScope val scope: ImportInfoFragmentInliningScope
) : JsVisitorWithContextImpl() { ) : JsVisitorWithContextImpl() {
override fun endVisit(x: JsExpressionStatement, ctx: JsContext<*>) { override fun visit(x: JsInvocation, ctx: JsContext<JsNode>): Boolean {
val e = x.expression // Is it `defineInlineFunction('tag', ...)`?
if (e is JsBinaryOperation) { InlineMetadata.decompose(x)?.let { metadata ->
if (e.operator == JsBinaryOperator.ASG) { val fn = metadata.function
e.arg2?.let { argument2 -> if (fn.function.coroutineMetadata != null) {
val splitSuspendInlineFunction = splitExportedSuspendInlineFunctionDeclarations(argument2) // This function will be exported to JS
if (splitSuspendInlineFunction != null) { ctx.replaceMe(scope.importFunctionDefinition(InlineFunctionDefinition(fn, metadata.tag.value)))
e.arg2 = splitSuspendInlineFunction
} // Original function should be not be transformed into a state machine
} fn.function.name = null
fn.function.coroutineMetadata = null
fn.function.isInlineableCoroutineBody = true
// Keep the `defineInlineFunction` for the inliner to find
lastStatementLevelContext.addNext(x.makeStmt())
} }
return false
} }
super.endVisit(x, ctx) // Is it `wrapFunction(...)`? Which means it'a a private inline function
} InlineMetadata.tryExtractFunction(x)?.let { fn ->
if (fn.function.coroutineMetadata != null) {
override fun endVisit(x: JsVars.JsVar, ctx: JsContext<*>) { // This function will be exported to JS
val initExpression = x.initExpression ?: return ctx.replaceMe(scope.importFunctionDefinition(InlineFunctionDefinition(fn, null)))
val splitSuspendInlineFunction = splitExportedSuspendInlineFunctionDeclarations(initExpression)
if (splitSuspendInlineFunction != null) {
x.initExpression = splitSuspendInlineFunction
}
}
private fun splitExportedSuspendInlineFunctionDeclarations(expression: JsExpression): JsFunction? {
val inlineMetadata = InlineMetadata.decompose(expression)
if (inlineMetadata != null) {
inlineMetadata.function.let { f ->
if (f.function.coroutineMetadata != null) {
val statementContext = lastStatementLevelContext
// This function will be exported to JS
val function = scope.importFunctionDefinition(InlineFunctionDefinition(inlineMetadata.function, inlineMetadata.tag.value))
// Original function should be not be transformed into a state machine
f.function.name = null
f.function.coroutineMetadata = null
f.function.isInlineableCoroutineBody = true
// Keep the `defineInlineFunction` for the inliner to find
statementContext.addNext(expression.makeStmt())
// Return the function body to be used without inlining.
return function
}
} }
return false
} }
return null
return super.visit(x, ctx)
} }
} }
@@ -172,6 +172,10 @@ class ImportInfoFragmentInliningScope private constructor(
fragment.declarationBlock.statements.addAll(0, additionalDeclarations) fragment.declarationBlock.statements.addAll(0, additionalDeclarations)
// post-processing // post-processing
// If run separately `private inline suspend fun`'s local declarations get inlined twice.
InlineSuspendFunctionSplitter(this).accept(allCode)
simplifyWrappedFunctions(allCode) simplifyWrappedFunctions(allCode)
removeUnusedFunctionDefinitions(allCode, collectNamedFunctions(allCode)) removeUnusedFunctionDefinitions(allCode, collectNamedFunctions(allCode))
removeUnusedImports(fragment, allCode) removeUnusedImports(fragment, allCode)