JS: transform coroutines before serializing AST to binary format

This commit is contained in:
Alexey Andreev
2017-02-27 20:08:09 +03:00
parent 02ca374dc9
commit 22f24d13b9
9 changed files with 95 additions and 27 deletions
@@ -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<JsStatement> {
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<JsStatement>()
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))
@@ -237,8 +237,9 @@ fun JsBlock.replaceSpecialReferences(context: CoroutineTransformationContext) {
visitor.accept(this)
}
fun JsBlock.replaceLocalVariables(scope: JsScope, context: CoroutineTransformationContext, localVariables: Set<JsName>) {
fun JsBlock.replaceLocalVariables(context: CoroutineTransformationContext, localVariables: Set<JsName>) {
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<in JsNode>) {
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<in JsStatement>) {
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}")
}
@@ -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<JsName, JsName>()
private val usedLocalVariableIds = mutableSetOf<String>()
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)
}
}
@@ -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
}
@@ -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");
@@ -119,11 +119,23 @@ public final class K2JSTranslator {
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled();
if (hasError(diagnostics)) return new TranslationResult.Fail(diagnostics);
List<JsProgramFragment> newFragments = translationResult.getFragments();
List<JsProgramFragment> allFragments = new ArrayList<JsProgramFragment>(newFragments);
List<JsProgramFragment> newFragments = new ArrayList<JsProgramFragment>(translationResult.getNewFragments());
List<JsProgramFragment> allFragments = new ArrayList<JsProgramFragment>(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<KtFile, FileTranslationResult> fileMap = new HashMap<KtFile, FileTranslationResult>();
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<String> importedModules = new ArrayList<>();
for (JsImportedModule module : translationResult.getImportedModuleList()) {
importedModules.add(module.getExternalName());
@@ -28,6 +28,7 @@ class AstGenerationResult(
val innerModuleName: JsName,
val fragments: List<JsProgramFragment>,
val fragmentMap: Map<KtFile, JsProgramFragment>,
val newFragments: List<JsProgramFragment>,
val fileMemberScopes: Map<KtFile, List<DeclarationDescriptor>>,
val importedModuleList: List<JsImportedModule>
)
@@ -270,6 +270,7 @@ public final class Translation {
Map<KtFile, JsProgramFragment> fragmentMap = new HashMap<KtFile, JsProgramFragment>();
List<JsProgramFragment> fragments = new ArrayList<JsProgramFragment>();
List<JsProgramFragment> newFragments = new ArrayList<JsProgramFragment>();
Map<KtFile, List<DeclarationDescriptor>> fileMemberScopes = new HashMap<KtFile, List<DeclarationDescriptor>>();
@@ -282,6 +283,7 @@ public final class Translation {
List<DeclarationDescriptor> fileMemberScope = new ArrayList<DeclarationDescriptor>();
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<JsProgramFragment> fragments) {
+36
View File
@@ -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<Unit> {
override val context = EmptyCoroutineContext
override fun resume(result: Unit) {}
override fun resumeWithException(exception: Throwable) {}
})
}
fun box(): String {
var result = ""
builder {
result = suspendHere()
}
return result
}