From 22f24d13b9f2558df3d2cf259f11a1b313c48d39 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Mon, 27 Feb 2017 20:08:09 +0300 Subject: [PATCH] JS: transform coroutines before serializing AST to binary format --- .../coroutine/CoroutineFunctionTransformer.kt | 12 +++---- .../kotlin/js/coroutine/CoroutinePasses.kt | 11 +++--- .../CoroutineTransformationContext.kt | 20 +++++++++-- .../serialization/js/ast/JsAstDeserializer.kt | 4 +++ .../js/test/semantics/BoxJsTestGenerated.java | 6 ++++ .../kotlin/js/facade/K2JSTranslator.java | 25 +++++++------ .../translate/general/AstGenerationResult.kt | 1 + .../js/translate/general/Translation.java | 7 +++- .../testData/box/incremental/coroutines.kt | 36 +++++++++++++++++++ 9 files changed, 95 insertions(+), 27 deletions(-) create mode 100644 js/js.translator/testData/box/incremental/coroutines.kt diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineFunctionTransformer.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineFunctionTransformer.kt index d5005f119c9..a90a114d669 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineFunctionTransformer.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineFunctionTransformer.kt @@ -30,7 +30,7 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f private val body = functionWithBody.body private val localVariables = (function.collectLocalVariables() + functionWithBody.collectLocalVariables() - functionWithBody.parameters.last().name).toMutableSet() - private val className = function.scope.parent.declareFreshName("Coroutine\$${name ?: "anonymous"}") + private val className = JsScope.declareTemporaryName("Coroutine\$${name ?: "anonymous"}") fun transform(): List { val context = CoroutineTransformationContext(function.scope, function) @@ -41,7 +41,7 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f val globalCatchBlockIndex = coroutineBlocks.indexOf(context.globalCatchBlock) coroutineBlocks.forEach { it.jsBlock.collectAdditionalLocalVariables() } - coroutineBlocks.forEach { it.jsBlock.replaceLocalVariables(function.scope, context, localVariables) } + coroutineBlocks.forEach { it.jsBlock.replaceLocalVariables(context, localVariables) } val additionalStatements = mutableListOf() generateDoResume(coroutineBlocks, context, additionalStatements) @@ -67,7 +67,7 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f val lastParameter = parameters.lastOrNull()?.name val controllerName = if (context.metadata.hasController) { - function.scope.declareFreshName("controller").apply { + JsScope.declareTemporaryName("controller").apply { constructor.parameters.add(constructor.parameters.lastIndex, JsParameter(this)) } } @@ -90,7 +90,7 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f } for (localVariable in localVariables) { val value = if (localVariable !in parameterNames) Namer.getUndefinedExpression() else localVariable.makeRef() - assignToField(function.scope.getFieldName(localVariable), value) + assignToField(context.getFieldName(localVariable), value) } } @@ -157,10 +157,10 @@ class CoroutineFunctionTransformer(private val program: JsProgram, private val f instantiation.arguments += parameters.last().name.makeRef() - val suspendedName = functionWithBody.scope.declareFreshName("suspended") + val suspendedName = JsScope.declareTemporaryName("suspended") functionWithBody.parameters += JsParameter(suspendedName) - val instanceName = functionWithBody.scope.declareFreshName("instance") + val instanceName = JsScope.declareTemporaryName("instance") functionWithBody.body.statements += JsAstUtils.newVar(instanceName, instantiation) val invokeResume = JsReturn(JsInvocation(JsNameRef(context.metadata.doResumeName, instanceName.makeRef()), JsLiteral.NULL)) diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutinePasses.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutinePasses.kt index 21bb9ff1e46..3568246df53 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutinePasses.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutinePasses.kt @@ -237,8 +237,9 @@ fun JsBlock.replaceSpecialReferences(context: CoroutineTransformationContext) { visitor.accept(this) } -fun JsBlock.replaceLocalVariables(scope: JsScope, context: CoroutineTransformationContext, localVariables: Set) { +fun JsBlock.replaceLocalVariables(context: CoroutineTransformationContext, localVariables: Set) { replaceSpecialReferences(context) + val visitor = object : JsVisitorWithContextImpl() { override fun visit(x: JsFunction, ctx: JsContext<*>): Boolean = false @@ -260,14 +261,14 @@ fun JsBlock.replaceLocalVariables(scope: JsScope, context: CoroutineTransformati override fun endVisit(x: JsNameRef, ctx: JsContext) { if (x.qualifier == null && x.name in localVariables) { - val fieldName = scope.getFieldName(x.name!!) + val fieldName = context.getFieldName(x.name!!) ctx.replaceMe(JsNameRef(fieldName, JsLiteral.THIS)) } } override fun endVisit(x: JsVars, ctx: JsContext) { val assignments = x.vars.mapNotNull { - val fieldName = scope.getFieldName(it.name) + val fieldName = context.getFieldName(it.name) val initExpression = it.initExpression if (initExpression != null) { JsAstUtils.assignment(JsNameRef(fieldName, JsLiteral.THIS), it.initExpression) @@ -286,6 +287,4 @@ fun JsBlock.replaceLocalVariables(scope: JsScope, context: CoroutineTransformati } } visitor.accept(this) -} - -fun JsScope.getFieldName(variableName: JsName) = declareName("local\$${variableName.ident}") \ No newline at end of file +} \ No newline at end of file diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineTransformationContext.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineTransformationContext.kt index 62bf2b9ff9a..57f3c0d0ca2 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineTransformationContext.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/coroutine/CoroutineTransformationContext.kt @@ -17,14 +17,28 @@ package org.jetbrains.kotlin.js.coroutine import org.jetbrains.kotlin.js.backend.ast.JsFunction +import org.jetbrains.kotlin.js.backend.ast.JsName import org.jetbrains.kotlin.js.backend.ast.JsScope import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata class CoroutineTransformationContext(private val scope: JsScope, function: JsFunction) { + private val localVariableNameCache = mutableMapOf() + private val usedLocalVariableIds = mutableSetOf() + val entryBlock = CoroutineBlock() val globalCatchBlock = CoroutineBlock() val metadata = function.coroutineMetadata!! - val controllerFieldName by lazy { scope.declareFreshName("\$controller") } - val returnValueFieldName by lazy { scope.declareFreshName("\$returnValue") } - val receiverFieldName by lazy { scope.declareFreshName("\$this") } + val controllerFieldName by lazy { scope.declareName("\$controller") } + val returnValueFieldName by lazy { scope.declareName("\$returnValue") } + val receiverFieldName by lazy { scope.declareName("\$this") } + + fun getFieldName(variableName: JsName) = localVariableNameCache.getOrPut(variableName) { + val baseId = "local\$${variableName.ident}" + var suggestedId = baseId + var suffix = 0 + while (!usedLocalVariableIds.add(suggestedId)) { + suggestedId = "${baseId}_${suffix++}" + } + scope.declareName(suggestedId) + } } \ No newline at end of file diff --git a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstDeserializer.kt b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstDeserializer.kt index 89836901ed0..ab72b067dbc 100644 --- a/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstDeserializer.kt +++ b/js/js.serializer/src/org/jetbrains/kotlin/serialization/js/ast/JsAstDeserializer.kt @@ -87,6 +87,10 @@ class JsAstDeserializer(private val program: JsProgram) { deserializeString(inlineModuleProto.signatureId) to moduleExpressions[inlineModuleProto.expressionId] } + for (nameBinding in fragment.nameBindings) { + nameBinding.name.imported = nameBinding.key in fragment.imports + } + return fragment } diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index 568c590edf9..c1bb098d182 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -3443,6 +3443,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/box/incremental"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS, true); } + @TestMetadata("coroutines.kt") + public void testCoroutines() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/incremental/coroutines.kt"); + doTest(fileName); + } + @TestMetadata("inline.kt") public void testInline() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/incremental/inline.kt"); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.java index 7b7571f23e4..0d5323ff30a 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.java @@ -119,11 +119,23 @@ public final class K2JSTranslator { ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); if (hasError(diagnostics)) return new TranslationResult.Fail(diagnostics); - List newFragments = translationResult.getFragments(); - List allFragments = new ArrayList(newFragments); + List newFragments = new ArrayList(translationResult.getNewFragments()); + List allFragments = new ArrayList(translationResult.getFragments()); JsInliner.process(config, analysisResult.getBindingTrace(), translationResult.getInnerModuleName(), allFragments, newFragments); + CoroutineTransformer coroutineTransformer = new CoroutineTransformer(translationResult.getProgram()); + for (JsProgramFragment fragment : newFragments) { + coroutineTransformer.accept(fragment.getDeclarationBlock()); + coroutineTransformer.accept(fragment.getInitializerBlock()); + } + RemoveUnusedImportsKt.removeUnusedImports(translationResult.getProgram()); + ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); + if (hasError(diagnostics)) return new TranslationResult.Fail(diagnostics); + + ExpandIsCallsKt.expandIsCalls(newFragments); + ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); + Map fileMap = new HashMap(); JsAstSerializer serializer = new JsAstSerializer(); boolean serializeFragments = config.getConfiguration().get(JSConfigurationKeys.SERIALIZE_FRAGMENTS, false); @@ -152,15 +164,6 @@ public final class K2JSTranslator { ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); if (hasError(diagnostics)) return new TranslationResult.Fail(diagnostics); - CoroutineTransformer coroutineTransformer = new CoroutineTransformer(translationResult.getProgram()); - coroutineTransformer.accept(translationResult.getProgram()); - RemoveUnusedImportsKt.removeUnusedImports(translationResult.getProgram()); - ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); - if (hasError(diagnostics)) return new TranslationResult.Fail(diagnostics); - - ExpandIsCallsKt.expandIsCalls(newFragments); - ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); - List importedModules = new ArrayList<>(); for (JsImportedModule module : translationResult.getImportedModuleList()) { importedModules.add(module.getExternalName()); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/AstGenerationResult.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/AstGenerationResult.kt index 6a8ecd84d4a..4df95baacfb 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/AstGenerationResult.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/AstGenerationResult.kt @@ -28,6 +28,7 @@ class AstGenerationResult( val innerModuleName: JsName, val fragments: List, val fragmentMap: Map, + val newFragments: List, val fileMemberScopes: Map>, val importedModuleList: List ) \ No newline at end of file diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java index 887c193acc6..e6817a121ca 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/general/Translation.java @@ -270,6 +270,7 @@ public final class Translation { Map fragmentMap = new HashMap(); List fragments = new ArrayList(); + List newFragments = new ArrayList(); Map> fileMemberScopes = new HashMap>(); @@ -282,6 +283,7 @@ public final class Translation { List fileMemberScope = new ArrayList(); translateFile(context, file, fileMemberScope); fragments.add(staticContext.getFragment()); + newFragments.add(staticContext.getFragment()); fragmentMap.put(file, staticContext.getFragment()); fileMemberScopes.put(file, fileMemberScope); merger.addFragment(staticContext.getFragment()); @@ -296,6 +298,7 @@ public final class Translation { JsProgramFragment testFragment = mayBeGenerateTests(config, bindingTrace, moduleDescriptor); fragments.add(testFragment); + newFragments.add(testFragment); merger.addFragment(testFragment); rootFunction.getParameters().add(new JsParameter(internalModuleName)); @@ -304,6 +307,7 @@ public final class Translation { bindingTrace, config, moduleDescriptor, mainCallParameters.arguments()); if (mainCallFragment != null) { fragments.add(mainCallFragment); + newFragments.add(mainCallFragment); merger.addFragment(mainCallFragment); } } @@ -333,7 +337,8 @@ public final class Translation { block.getStatements().addAll(wrapIfNecessary(config.getModuleId(), rootFunction, importedModuleList, program, config.getModuleKind())); - return new AstGenerationResult(program, internalModuleName, fragments, fragmentMap, fileMemberScopes, importedModuleList); + return new AstGenerationResult(program, internalModuleName, fragments, fragmentMap, newFragments, + fileMemberScopes, importedModuleList); } private static boolean isBuiltinModule(@NotNull List fragments) { diff --git a/js/js.translator/testData/box/incremental/coroutines.kt b/js/js.translator/testData/box/incremental/coroutines.kt new file mode 100644 index 00000000000..ab061709b6d --- /dev/null +++ b/js/js.translator/testData/box/incremental/coroutines.kt @@ -0,0 +1,36 @@ +// FILE: a.kt +// WITH_RUNTIME +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* + +suspend fun suspendThere(v: String): String = suspendCoroutineOrReturn { x -> + x.resume(v) + COROUTINE_SUSPENDED +} + +suspend fun suspendHere(): String = suspendThere("O") + suspendThere("K") + +// FILE: b.kt +// RECOMPILE +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* + +fun builder(c: suspend () -> Unit) { + c.startCoroutine(object : Continuation { + override val context = EmptyCoroutineContext + + override fun resume(result: Unit) {} + + override fun resumeWithException(exception: Throwable) {} + }) +} + +fun box(): String { + var result = "" + + builder { + result = suspendHere() + } + + return result +}