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)
}
}