JS: postpone JS AST merging

This commit is contained in:
Anton Bannykh
2018-11-15 15:58:38 +03:00
parent ec276dbea4
commit c65383fa3f
12 changed files with 613 additions and 643 deletions
@@ -16,4 +16,4 @@
package org.jetbrains.kotlin.js.backend.ast package org.jetbrains.kotlin.js.backend.ast
class JsNameBinding(val key: String, var name: JsName) data class JsNameBinding(val key: String, var name: JsName)
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.js.coroutine
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.clean.LabeledBlockToDoWhileTransformation
import org.jetbrains.kotlin.js.translate.declaration.transformCoroutineMetadataToSpecialFunctions import org.jetbrains.kotlin.js.translate.declaration.transformCoroutineMetadataToSpecialFunctions
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata import org.jetbrains.kotlin.js.translate.expression.InlineMetadata
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
@@ -66,3 +67,11 @@ class CoroutineTransformer : JsVisitorWithContextImpl() {
return super.visit(x, ctx) return super.visit(x, ctx)
} }
} }
fun transformCoroutines(fragments: List<JsProgramFragment>) {
val coroutineTransformer = CoroutineTransformer()
for (fragment in fragments) {
coroutineTransformer.accept(fragment.declarationBlock)
coroutineTransformer.accept(fragment.initializerBlock)
}
}
@@ -5,11 +5,6 @@
package org.jetbrains.kotlin.js.inline package org.jetbrains.kotlin.js.inline
import org.jetbrains.kotlin.backend.common.*
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
@@ -18,7 +13,6 @@ import org.jetbrains.kotlin.js.config.JsConfig
import org.jetbrains.kotlin.js.inline.clean.* import org.jetbrains.kotlin.js.inline.clean.*
import org.jetbrains.kotlin.js.inline.context.FunctionContext import org.jetbrains.kotlin.js.inline.context.FunctionContext
import org.jetbrains.kotlin.js.inline.context.InliningContext 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.*
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata import org.jetbrains.kotlin.js.translate.expression.InlineMetadata
import org.jetbrains.kotlin.resolve.inline.InlineStrategy import org.jetbrains.kotlin.resolve.inline.InlineStrategy
@@ -28,6 +22,7 @@ import java.util.*
import org.jetbrains.kotlin.js.inline.util.getImportTag import org.jetbrains.kotlin.js.inline.util.getImportTag
import org.jetbrains.kotlin.js.inline.util.IdentitySet import org.jetbrains.kotlin.js.inline.util.IdentitySet
import org.jetbrains.kotlin.js.translate.declaration.transformSpecialFunctionsToCoroutineMetadata import org.jetbrains.kotlin.js.translate.declaration.transformSpecialFunctionsToCoroutineMetadata
import org.jetbrains.kotlin.js.translate.general.AstGenerationResult
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.flattenStatement import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.flattenStatement
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.pureFqn import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.pureFqn
@@ -37,17 +32,17 @@ class JsInliner private constructor(
private val accessors: Map<String, FunctionWithWrapper>, private val accessors: Map<String, FunctionWithWrapper>,
private val inverseNameBindings: Map<JsName, String>, private val inverseNameBindings: Map<JsName, String>,
private val functionReader: FunctionReader, private val functionReader: FunctionReader,
private val trace: DiagnosticSink private val trace: DiagnosticSink,
) : JsVisitorWithContextImpl() { private val moduleMap: Map<JsName, JsImportedModule>
) {
private val namedFunctionsSet: MutableSet<JsFunction> = functions.values.mapTo(IdentitySet()) { it.function } private val namedFunctionsSet: MutableSet<JsFunction> = functions.values.mapTo(IdentitySet()) { it.function }
private val inliningContexts = Stack<JsInliningContext>()
private val processedFunctions = IdentitySet<JsFunction>() private val processedFunctions = IdentitySet<JsFunction>()
private val inProcessFunctions = IdentitySet<JsFunction>() private val inProcessFunctions = IdentitySet<JsFunction>()
private var existingImports: MutableMap<String, JsName> = HashMap()
private var statementContextForInline: JsContext<JsStatement>? = null
private val functionsByWrapperNodes = HashMap<JsBlock, FunctionWithWrapper>() private val functionsByWrapperNodes = HashMap<JsBlock, FunctionWithWrapper>()
private val functionsByFunctionNodes = HashMap<JsFunction, FunctionWithWrapper>() private val functionsByFunctionNodes = HashMap<JsFunction, FunctionWithWrapper>()
init { init {
(functions.values.asSequence() + accessors.values.asSequence()).forEach { f -> (functions.values.asSequence() + accessors.values.asSequence()).forEach { f ->
functionsByFunctionNodes[f.function] = f functionsByFunctionNodes[f.function] = f
@@ -60,442 +55,384 @@ class JsInliner private constructor(
// these are needed for error reporting, when inliner detects cycle // these are needed for error reporting, when inliner detects cycle
private val namedFunctionsStack = Stack<JsFunction>() private val namedFunctionsStack = Stack<JsFunction>()
private val inlineCallInfos = LinkedList<JsCallInfo>() private val inlineCallInfos = LinkedList<JsCallInfo>()
private val canBeExtractedByInliner: (JsNode) -> Boolean = { node -> node is JsInvocation && hasToBeInlined(node) }
private var inlineFunctionDepth: Int = 0
private val replacementsInducedByWrappers = HashMap<JsWrapperKey, Map<JsName, JsNameRef>>()
private var existingNameBindings: MutableMap<JsName, String> = HashMap()
private val additionalNameBindings = ArrayList<JsNameBinding>()
private val inlinedModuleAliases = HashSet<JsName>()
private val inliningContext: JsInliningContext
get() = inliningContexts.peek()
private val functionContext: FunctionContext
get() = inliningContext.functionContext
private val currentNamedFunction: JsFunction? private val currentNamedFunction: JsFunction?
get() = if (namedFunctionsStack.empty()) null else namedFunctionsStack.peek() get() = if (namedFunctionsStack.empty()) null else namedFunctionsStack.peek()
private fun addInlinedModules(fragment: JsProgramFragment, moduleMap: Map<JsName, JsImportedModule>) { private inner class InlinerImpl(
val localMap = buildModuleMap(listOf(fragment)).keys val existingNameBindings: MutableMap<JsName, String>,
for (inlinedModuleName in inlinedModuleAliases) { val existingImports: MutableMap<String, JsName>,
if (!localMap.contains(inlinedModuleName)) { val inlineFunctionDepth: Int,
fragment.importedModules.add(moduleMap[inlinedModuleName]!!) val addPrevious: (JsStatement) -> Unit
} ) : JsVisitorWithContextImpl() {
}
}
private fun processImportStatement(statement: JsStatement) { val replacementsInducedByWrappers = HashMap<JsFunction, Map<JsName, JsNameRef>>()
if (statement is JsVars) {
val tag = getImportTag(statement)
if (tag != null) {
existingImports[tag] = statement.vars[0].name
}
}
}
override fun visit(function: JsFunction, context: JsContext<*>): Boolean { val inlinedModuleAliases = HashSet<JsName>()
val functionWithWrapper = functionsByFunctionNodes[function]
if (functionWithWrapper != null) { val additionalNameBindings = ArrayList<JsNameBinding>()
visit(functionWithWrapper)
return false override fun visit(function: JsFunction, context: JsContext<*>): Boolean {
} else { val functionWithWrapper = functionsByFunctionNodes[function]
if (statementContextForInline == null) { if (functionWithWrapper != null) {
statementContextForInline = lastStatementLevelContext visit(functionWithWrapper)
startFunction(function) return false
val result = super.visit(function, context)
statementContextForInline = null
return result
} else { } else {
startFunction(function) startFunction(function)
return super.visit(function, context) return super.visit(function, context)
} }
} }
}
override fun endVisit(function: JsFunction, context: JsContext<*>) { override fun endVisit(function: JsFunction, context: JsContext<*>) {
super.endVisit(function, context) super.endVisit(function, context)
if (!functionsByFunctionNodes.containsKey(function)) { if (!functionsByFunctionNodes.containsKey(function)) {
endFunction(function) endFunction(function)
}
}
private fun startFunction(function: JsFunction) {
inliningContexts.push(JsInliningContext(statementContextForInline!!))
assert(!inProcessFunctions.contains(function)) { "Inliner has revisited function" }
inProcessFunctions.add(function)
if (namedFunctionsSet.contains(function)) {
namedFunctionsStack.push(function)
}
}
private fun endFunction(function: JsFunction) {
refreshLabelNames(function.body, function.scope)
removeUnusedLocalFunctionDeclarations(function)
processedFunctions.add(function)
FunctionPostProcessor(function).apply()
assert(inProcessFunctions.contains(function))
inProcessFunctions.remove(function)
inliningContexts.pop()
if (!namedFunctionsStack.empty() && namedFunctionsStack.peek() == function) {
namedFunctionsStack.pop()
}
}
override fun visit(x: JsBlock, ctx: JsContext<*>): Boolean {
val functionWithWrapper = functionsByWrapperNodes[x]
if (functionWithWrapper != null) {
visit(functionWithWrapper)
return false
}
return super.visit(x, ctx)
}
private fun visit(functionWithWrapper: FunctionWithWrapper) {
val oldContextForInline = statementContextForInline
val oldExistingImports = existingImports
val oldInlineFunctionDepth = inlineFunctionDepth
val innerContext = ListContext<JsStatement>()
val wrapperBody = functionWithWrapper.wrapperBody
var statements: MutableList<JsStatement>? = null
if (wrapperBody != null) {
existingImports = HashMap()
statementContexts.push(innerContext)
statementContextForInline = innerContext
inlineFunctionDepth++
for (statement in wrapperBody.statements) {
processImportStatement(statement)
} }
statements = wrapperBody.statements }
if (!statements.isEmpty() && statements[statements.size - 1] is JsReturn) {
statements = statements.subList(0, statements.size - 1)
private fun startFunction(function: JsFunction) {
assert(!inProcessFunctions.contains(function)) { "Inliner has revisited function" }
inProcessFunctions.add(function)
if (namedFunctionsSet.contains(function)) {
namedFunctionsStack.push(function)
}
}
private fun endFunction(function: JsFunction) {
refreshLabelNames(function.body, function.scope)
removeUnusedLocalFunctionDeclarations(function)
processedFunctions.add(function)
FunctionPostProcessor(function).apply()
assert(inProcessFunctions.contains(function))
inProcessFunctions.remove(function)
if (!namedFunctionsStack.empty() && namedFunctionsStack.peek() == function) {
namedFunctionsStack.pop()
}
}
override fun visit(x: JsBlock, ctx: JsContext<*>): Boolean {
val functionWithWrapper = functionsByWrapperNodes[x]
if (functionWithWrapper != null) {
visit(functionWithWrapper)
return false
}
return super.visit(x, ctx)
}
private fun visit(functionWithWrapper: FunctionWithWrapper) {
startFunction(functionWithWrapper.function)
val wrapperBody = functionWithWrapper.wrapperBody
if (wrapperBody != null) {
// statementContexts.push(ListContext())
val existingImports = HashMap<String, JsName>()
for (statement in wrapperBody.statements) {
if (statement is JsVars) {
val tag = getImportTag(statement)
if (tag != null) {
existingImports[tag] = statement.vars[0].name
}
}
}
val additionalStatements = mutableListOf<JsStatement>()
val innerInliner = InlinerImpl(existingNameBindings, existingImports, inlineFunctionDepth + 1) {
additionalStatements.add(it)
}
for (statement in wrapperBody.statements) {
if (statement !is JsReturn) {
innerInliner.acceptStatement(statement)
} else {
innerInliner.accept((statement.expression as JsFunction).body)
}
}
// innerInliner.acceptStatement(wrapperBody)
// TODO keep order
wrapperBody.statements.addAll(0, additionalStatements)
// statementContexts.pop()
} else {
accept(functionWithWrapper.function.body)
} }
innerContext.traverse(statements) endFunction(functionWithWrapper.function)
statementContexts.pop()
} else {
if (statementContextForInline == null) statementContextForInline = lastStatementLevelContext
} }
startFunction(functionWithWrapper.function) override fun visit(call: JsInvocation, context: JsContext<*>): Boolean {
if (!hasToBeInlined(call)) return true
val block = JsBlock(functionWithWrapper.function.body) currentNamedFunction?.let {
innerContext.traverse(block.statements) inlineCallInfos.add(JsCallInfo(call, it))
functionWithWrapper.function.body.traverse(this, innerContext)
endFunction(functionWithWrapper.function)
statements?.addAll(block.statements.subList(0, block.statements.size - 1))
statementContextForInline = oldContextForInline
existingImports = oldExistingImports
inlineFunctionDepth = oldInlineFunctionDepth
}
override fun visit(call: JsInvocation, context: JsContext<*>): Boolean {
if (!hasToBeInlined(call)) return true
val containingFunction = currentNamedFunction
if (containingFunction != null) {
inlineCallInfos.add(JsCallInfo(call, containingFunction))
}
val definition = functionContext.getFunctionDefinition(call)
if (inProcessFunctions.contains(definition.function)) {
reportInlineCycle(call, definition.function)
} else if (!processedFunctions.contains(definition.function)) {
for (i in 0 until call.arguments.size) {
val argument = call.arguments[i]
call.arguments[i] = accept(argument)
} }
visit(definition)
return false val definition = functionContext.getFunctionDefinition(call)
if (inProcessFunctions.contains(definition.function)) {
reportInlineCycle(call, definition.function)
} else if (!processedFunctions.contains(definition.function)) {
for (i in 0 until call.arguments.size) {
val argument = call.arguments[i]
call.arguments[i] = accept(argument)
}
visit(definition)
return false
}
return true
} }
return true override fun endVisit(x: JsInvocation, ctx: JsContext<JsNode>) {
} if (hasToBeInlined(x)) {
inline(x, ctx)
override fun endVisit(x: JsInvocation, ctx: JsContext<JsNode>) { }
if (hasToBeInlined(x)) { if (!inlineCallInfos.isEmpty()) {
inline(x, ctx) if (inlineCallInfos.last.call == x) {
inlineCallInfos.removeLast()
}
}
} }
var lastCallInfo: JsCallInfo? = null override fun endVisit(x: JsExpressionStatement, ctx: JsContext<*>) {
val e = x.expression
if (!inlineCallInfos.isEmpty()) { if (e is JsBinaryOperation) {
lastCallInfo = inlineCallInfos.last if (e.operator == JsBinaryOperator.ASG) {
} e.arg2?.let { argument2 ->
if (lastCallInfo != null && lastCallInfo.call == x) {
inlineCallInfos.removeLast()
}
}
override fun endVisit(x: JsExpressionStatement, ctx: JsContext<*>) {
val e = x.expression
if (e is JsBinaryOperation) {
if (e.operator == JsBinaryOperator.ASG) {
e.arg2?.let { argument2 ->
val splitSuspendInlineFunction = splitExportedSuspendInlineFunctionDeclarations(argument2) val splitSuspendInlineFunction = splitExportedSuspendInlineFunctionDeclarations(argument2)
if (splitSuspendInlineFunction != null) { if (splitSuspendInlineFunction != null) {
e.arg2 = splitSuspendInlineFunction e.arg2 = splitSuspendInlineFunction
} }
} }
}}
super.endVisit(x, ctx)
}
override fun endVisit(x: JsVars.JsVar, ctx: JsContext<*>) {
val initExpression = x.initExpression ?: return
val splitSuspendInlineFunction = splitExportedSuspendInlineFunctionDeclarations(initExpression)
if (splitSuspendInlineFunction != null) {
x.initExpression = splitSuspendInlineFunction
} }
} }
super.endVisit(x, ctx) private fun splitExportedSuspendInlineFunctionDeclarations(expression: JsExpression): JsFunction? {
} val inlineMetadata = InlineMetadata.decompose(expression)
if (inlineMetadata != null) {
val (originalFunction, wrapperBody) = inlineMetadata.function
if (originalFunction.coroutineMetadata != null) {
val statementContext = lastStatementLevelContext
override fun endVisit(x: JsVars.JsVar, ctx: JsContext<*>) { // This function will be exported to JS
val initExpression = x.initExpression ?: return val function = originalFunction.deepCopy()
val splitSuspendInlineFunction = splitExportedSuspendInlineFunctionDeclarations(initExpression) // Original function should be not be transformed into a state machine
if (splitSuspendInlineFunction != null) { originalFunction.setName(null)
x.initExpression = splitSuspendInlineFunction originalFunction.coroutineMetadata = null
} originalFunction.isInlineableCoroutineBody = true
} if (wrapperBody != null) {
// Extract local declarations
applyWrapper(wrapperBody, function, originalFunction)
}
private fun splitExportedSuspendInlineFunctionDeclarations(expression: JsExpression): JsFunction? { // Keep the `defineInlineFunction` for the inliner to find
val inlineMetadata = InlineMetadata.decompose(expression) statementContext.addNext(expression.makeStmt())
if (inlineMetadata != null) {
val (originalFunction, wrapperBody) = inlineMetadata.function
if (originalFunction.coroutineMetadata != null) {
val statementContext = lastStatementLevelContext
// This function will be exported to JS // Return the function body to be used without inlining.
val function = originalFunction.deepCopy() return function
// Original function should be not be transformed into a state machine
originalFunction.setName(null)
originalFunction.coroutineMetadata = null
originalFunction.isInlineableCoroutineBody = true
if (wrapperBody != null) {
// Extract local declarations
applyWrapper(wrapperBody, function, originalFunction, JsInliningContext(statementContext))
} }
// Keep the `defineInlineFunction` for the inliner to find
statementContext.addNext(expression.makeStmt())
// Return the function body to be used without inlining.
return function
} }
return null
} }
return null
}
override fun doAcceptStatementList(statements: MutableList<JsStatement>) { override fun doAcceptStatementList(statements: MutableList<JsStatement>) {
// at top level of js ast, contexts stack can be empty,
// but there is no inline calls anyway
if (!inliningContexts.isEmpty()) {
var i = 0 var i = 0
while (i < statements.size) { while (i < statements.size) {
val additionalStatements = ExpressionDecomposer.preserveEvaluationOrder(statements[i], canBeExtractedByInliner) val additionalStatements =
ExpressionDecomposer.preserveEvaluationOrder(statements[i]) { node -> node is JsInvocation && hasToBeInlined(node) }
statements.addAll(i, additionalStatements) statements.addAll(i, additionalStatements)
i += additionalStatements.size + 1 i += additionalStatements.size + 1
} }
super.doAcceptStatementList(statements)
} }
super.doAcceptStatementList(statements) private fun inline(call: JsInvocation, context: JsContext<JsNode>) {
} var functionWithWrapper = functionContext.getFunctionDefinition(call)
private fun inline(call: JsInvocation, context: JsContext<JsNode>) { // Since we could get functionWithWrapper as a simple function directly from staticRef (which always points on implementation)
val callDescriptor = call.descriptor // we should check if we have a known wrapper for it
if (isSuspendWithCurrentContinuation( functionsByFunctionNodes[functionWithWrapper.function]?.let {
callDescriptor, functionWithWrapper = it
config.configuration.languageVersionSettings
)
) {
inlineSuspendWithCurrentContinuation(call, context)
return
}
val inliningContext = inliningContext
var functionWithWrapper = inliningContext.functionContext.getFunctionDefinition(call)
// Since we could get functionWithWrapper as a simple function directly from staticRef (which always points on implementation)
// we should check if we have a known wrapper for it
functionsByFunctionNodes[functionWithWrapper.function]?.let {
functionWithWrapper = it
}
val function = functionWithWrapper.function.deepCopy()
function.body = transformSpecialFunctionsToCoroutineMetadata(function.body)
if (functionWithWrapper.wrapperBody != null) {
applyWrapper(functionWithWrapper.wrapperBody!!, function, functionWithWrapper.function, inliningContext)
}
val inlineableResult = FunctionInlineMutator.getInlineableCallReplacement(call, function, inliningContext)
val inlineableBody = inlineableResult.inlineableBody
var resultExpression = inlineableResult.resultExpression
val statementContext = inliningContext.statementContext
// body of inline function can contain call to lambdas that need to be inlined
val inlineableBodyWithLambdasInlined = accept(inlineableBody)
assert(inlineableBody === inlineableBodyWithLambdasInlined)
// Support non-local return from secondary constructor
// Returns from secondary constructors should return `$this` object.
val currentFunction = currentNamedFunction
if (currentFunction != null) {
val returnVariable = currentFunction.forcedReturnVariable
if (returnVariable != null) {
inlineableBody.accept(object : RecursiveJsVisitor() {
override fun visitReturn(x: JsReturn) {
x.expression = returnVariable.makeRef()
}
})
} }
}
statementContext.addPrevious(flattenStatement(inlineableBody)) val function = functionWithWrapper.function.deepCopy()
function.body = transformSpecialFunctionsToCoroutineMetadata(function.body)
if (functionWithWrapper.wrapperBody != null) {
applyWrapper(functionWithWrapper.wrapperBody!!, function, functionWithWrapper.function)
}
/* val inliningContext = InliningContext(lastStatementLevelContext)
* Assumes, that resultExpression == null, when result is not needed.
* @see FunctionInlineMutator.isResultNeeded()
*/
if (resultExpression == null) {
statementContext.removeMe()
return
}
resultExpression = accept(resultExpression) val inlineableResult = FunctionInlineMutator.getInlineableCallReplacement(call, function, inliningContext)
resultExpression.synthetic = true
context.replaceMe(resultExpression)
}
private fun applyWrapper( val inlineableBody = inlineableResult.inlineableBody
wrapper: JsBlock, function: JsFunction, originalFunction: JsFunction, var resultExpression = inlineableResult.resultExpression
inliningContext: InliningContext val statementContext = inliningContext.statementContext
) { // body of inline function can contain call to lambdas that need to be inlined
val key = JsWrapperKey(inliningContext.statementContextBeforeCurrentFunction, originalFunction) val inlineableBodyWithLambdasInlined = accept(inlineableBody)
assert(inlineableBody === inlineableBodyWithLambdasInlined)
// Apparently we should avoid this trick when we implement fair support for crossinline // Support non-local return from secondary constructor
val replacements = replacementsInducedByWrappers.computeIfAbsent(key) { k -> // Returns from secondary constructors should return `$this` object.
val ctx = k.context val currentFunction = currentNamedFunction
if (currentFunction != null) {
val newReplacements = HashMap<JsName, JsNameRef>() val returnVariable = currentFunction.forcedReturnVariable
if (returnVariable != null) {
val copiedStatements = ArrayList<JsStatement>() inlineableBody.accept(object : RecursiveJsVisitor() {
wrapper.statements.asSequence() override fun visitReturn(x: JsReturn) {
.filterNot { it is JsReturn } x.expression = returnVariable.makeRef()
.map { it.deepCopy() }
.forEach { statement ->
if (inlineFunctionDepth == 0) {
replaceExpressionsWithLocalAliases(statement)
}
if (statement is JsVars) {
val tag = getImportTag(statement)
if (tag != null) {
val name = statement.vars[0].name
var existingName: JsName? = if (inlineFunctionDepth == 0) name.localAlias else null
if (existingName == null) {
existingName = existingImports.computeIfAbsent(tag) {
copiedStatements.add(statement)
val alias = JsScope.declareTemporaryName(name.ident)
alias.copyMetadataFrom(name)
newReplacements[name] = pureFqn(alias, null)
alias
}
}
if (name !== existingName) {
val replacement = pureFqn(existingName, null)
newReplacements[name] = replacement
}
return@forEach
} }
})
}
}
statementContext.addPrevious(flattenStatement(inlineableBody))
/*
* Assumes, that resultExpression == null, when result is not needed.
* @see FunctionInlineMutator.isResultNeeded()
*/
if (resultExpression == null) {
statementContext.removeMe()
return
}
resultExpression = accept(resultExpression)
resultExpression.synthetic = true
context.replaceMe(resultExpression)
}
private fun applyWrapper(
wrapper: JsBlock, function: JsFunction, originalFunction: JsFunction
) {
// TODO Decrypt the comment below
// Apparently we should avoid this trick when we implement fair support for crossinline
val replacements = replacementsInducedByWrappers.computeIfAbsent(originalFunction) { k ->
val newReplacements = HashMap<JsName, JsNameRef>()
val copiedStatements = ArrayList<JsStatement>()
wrapper.statements.asSequence()
.filterNot { it is JsReturn }
.map { it.deepCopy() }
.forEach { statement ->
if (inlineFunctionDepth == 0) {
replaceExpressionsWithLocalAliases(statement)
}
if (statement is JsVars) {
val tag = getImportTag(statement)
if (tag != null) {
val name = statement.vars[0].name
var existingName: JsName? = if (inlineFunctionDepth == 0) name.localAlias else null
if (existingName == null) {
existingName = existingImports.computeIfAbsent(tag) {
copiedStatements.add(statement)
val alias = JsScope.declareTemporaryName(name.ident)
alias.copyMetadataFrom(name)
newReplacements[name] = pureFqn(alias, null)
alias
}
}
if (name !== existingName) {
val replacement = pureFqn(existingName, null)
newReplacements[name] = replacement
}
return@forEach
}
}
copiedStatements.add(statement)
} }
copiedStatements.add(statement) val definedNames = copiedStatements.asSequence()
.flatMap { node -> collectDefinedNamesInAllScopes(node).asSequence() }
.filter { name -> !newReplacements.containsKey(name) }
.toSet()
for (name in definedNames) {
val alias = JsScope.declareTemporaryName(name.ident)
alias.copyMetadataFrom(name)
val replacement = pureFqn(alias, null)
newReplacements[name] = replacement
} }
val definedNames = copiedStatements.asSequence() for (statement in copiedStatements) {
.flatMap { node -> collectDefinedNamesInAllScopes(node).asSequence() } addPrevious(accept(replaceNames(statement, newReplacements)))
.filter { name -> !newReplacements.containsKey(name) }
.toSet()
for (name in definedNames) {
val alias = JsScope.declareTemporaryName(name.ident)
alias.copyMetadataFrom(name)
val replacement = pureFqn(alias, null)
newReplacements[name] = replacement
}
for (statement in copiedStatements) {
ctx.addPrevious(accept(replaceNames(statement, newReplacements)))
}
for ((key, value) in collectNamedFunctions(JsBlock(copiedStatements))) {
if (key.staticRef is JsFunction) {
key.staticRef = value
} }
for ((key, value) in collectNamedFunctions(JsBlock(copiedStatements))) {
if (key.staticRef is JsFunction) {
key.staticRef = value
}
}
newReplacements
} }
newReplacements replaceNames(function, replacements)
}
replaceNames(function, replacements) // Copy nameBinding's for inlined localAlias'es
for (nameRef in replacements.values) {
// Copy nameBinding's for inlined localAlias'es val name = nameRef.name
for (nameRef in replacements.values) { if (name != null && !existingNameBindings.containsKey(name)) {
val name = nameRef.name val tag = inverseNameBindings[name]
if (name != null && !existingNameBindings.containsKey(name)) { if (tag != null) {
val tag = inverseNameBindings[name] existingNameBindings[name] = tag
if (tag != null) { additionalNameBindings.add(JsNameBinding(tag, name))
existingNameBindings[name] = tag }
additionalNameBindings.add(JsNameBinding(tag, name))
} }
} }
} }
}
private fun replaceExpressionsWithLocalAliases(statement: JsStatement) { private fun replaceExpressionsWithLocalAliases(statement: JsStatement) {
object : JsVisitorWithContextImpl() { object : JsVisitorWithContextImpl() {
override fun endVisit(x: JsNameRef, ctx: JsContext<JsNode>) { override fun endVisit(x: JsNameRef, ctx: JsContext<JsNode>) {
replaceIfNecessary(x, ctx) replaceIfNecessary(x, ctx)
}
override fun endVisit(x: JsArrayAccess, ctx: JsContext<JsNode>) {
replaceIfNecessary(x, ctx)
}
private fun replaceIfNecessary(expression: JsExpression, ctx: JsContext<JsNode>) {
val alias = expression.localAlias
if (alias != null) {
ctx.replaceMe(alias.makeRef())
inlinedModuleAliases.add(alias)
} }
}
}.accept(statement) override fun endVisit(x: JsArrayAccess, ctx: JsContext<JsNode>) {
} replaceIfNecessary(x, ctx)
}
private fun inlineSuspendWithCurrentContinuation(call: JsInvocation, context: JsContext<JsNode>) { private fun replaceIfNecessary(expression: JsExpression, ctx: JsContext<JsNode>) {
val lambda = call.arguments[0] val alias = expression.localAlias
val continuationArg = call.arguments[call.arguments.size - 1] if (alias != null) {
ctx.replaceMe(alias.makeRef())
inlinedModuleAliases.add(alias)
}
}
val invocation = JsInvocation(lambda, continuationArg) }.accept(statement)
invocation.isSuspend = true }
context.replaceMe(accept(invocation))
private fun hasToBeInlined(call: JsInvocation): Boolean {
val strategy = call.inlineStrategy
return if (strategy == null || !strategy.isInline) false else functionContext.hasFunctionDefinition(call)
}
} }
private fun reportInlineCycle(call: JsInvocation, calledFunction: JsFunction) { private fun reportInlineCycle(call: JsInvocation, calledFunction: JsFunction) {
@@ -517,50 +454,49 @@ class JsInliner private constructor(
} }
} }
private fun hasToBeInlined(call: JsInvocation): Boolean {
val strategy = call.inlineStrategy
return if (strategy == null || !strategy.isInline) false else functionContext.hasFunctionDefinition(call)
} private fun addInlinedModules(fragment: JsProgramFragment, inliner: InlinerImpl) {
val existingModules = fragment.importedModules.mapTo(IdentitySet()) { it.internalName }
private inner class JsInliningContext internal constructor(override val statementContextBeforeCurrentFunction: JsContext<JsStatement>) : inliner.inlinedModuleAliases.forEach {
InliningContext { if (it !in existingModules) {
override val functionContext: FunctionContext fragment.importedModules.add(moduleMap[it]!!.let {
// Copy so that the Merger.kt doesn't operate on the same instance in different fragments.
override val statementContext: JsContext<JsStatement> JsImportedModule(it.externalName, it.internalName, it.plainReference)
get() = lastStatementLevelContext })
init {
functionContext = object : FunctionContext(functionReader, config) {
override fun lookUpStaticFunction(functionName: JsName?): FunctionWithWrapper? {
return functions[functionName]
}
override fun lookUpStaticFunctionByTag(functionTag: String): FunctionWithWrapper? {
return accessors[functionTag]
}
} }
} }
}
override fun newNamingContext(): NamingContext {
return NamingContext(statementContext) val functionContext: FunctionContext = object : FunctionContext(functionReader, config) {
} override fun lookUpStaticFunction(functionName: JsName?): FunctionWithWrapper? = functions[functionName]
override fun lookUpStaticFunctionByTag(functionTag: String): FunctionWithWrapper? = accessors[functionTag]
} }
private class JsCallInfo(val call: JsInvocation, val containingFunction: JsFunction) private class JsCallInfo(val call: JsInvocation, val containingFunction: JsFunction)
internal class JsWrapperKey(val context: JsContext<JsStatement>, private val function: JsFunction) { fun process(fragment: JsProgramFragment, existingImports: MutableMap<String, JsName>) {
val existingNameBindings = fragment.nameBindings.associateTo(IdentityHashMap()) { it.name to it.key }
override fun equals(o: Any?): Boolean { val additionalDeclarations = mutableListOf<JsStatement>()
if (this === o) return true val inliner = InlinerImpl(existingNameBindings, existingImports, 0) {
if (o == null || javaClass != o.javaClass) return false additionalDeclarations.add(it)
val key = o as JsWrapperKey?
return context == key!!.context && function == key.function
} }
override fun hashCode(): Int { inliner.acceptStatement(fragment.declarationBlock)
return Objects.hash(context, function) // Mostly for the sake of post-processor
// TODO are inline function marked with @Test possible?
if (fragment.tests != null) {
inliner.acceptStatement(fragment.tests)
} }
inliner.acceptStatement(fragment.initializerBlock)
// TODO fix the order
fragment.declarationBlock.statements.addAll(0, additionalDeclarations)
fragment.nameBindings.addAll(inliner.additionalNameBindings)
addInlinedModules(fragment, inliner)
} }
companion object { companion object {
@@ -569,55 +505,27 @@ class JsInliner private constructor(
reporter: JsConfig.Reporter, reporter: JsConfig.Reporter,
config: JsConfig, config: JsConfig,
trace: DiagnosticSink, trace: DiagnosticSink,
currentModuleName: JsName, translationResult: AstGenerationResult
fragments: List<JsProgramFragment>,
fragmentsToProcess: List<JsProgramFragment>,
importStatements: List<JsStatement>
) { ) {
val functions = collectNamedFunctionsAndWrappers(fragments) val functions = collectNamedFunctionsAndWrappers(translationResult.newFragments)
val accessors = collectAccessors(fragments) val accessors = collectAccessors(translationResult.fragments)
val inverseNameBindings = collectNameBindings(fragments)
val inverseNameBindings = inverseNameBindings(*translationResult.fragments.toTypedArray())
val accessorInvocationTransformer = DummyAccessorInvocationTransformer() val accessorInvocationTransformer = DummyAccessorInvocationTransformer()
for (fragment in fragmentsToProcess) { for (fragment in translationResult.newFragments) {
accessorInvocationTransformer.accept<JsGlobalBlock>(fragment.declarationBlock) accessorInvocationTransformer.accept<JsGlobalBlock>(fragment.declarationBlock)
accessorInvocationTransformer.accept<JsGlobalBlock>(fragment.initializerBlock) accessorInvocationTransformer.accept<JsGlobalBlock>(fragment.initializerBlock)
} }
val functionReader = FunctionReader(reporter, config, currentModuleName, fragments) val functionReader = FunctionReader(reporter, config, translationResult.innerModuleName, translationResult.fragments)
val inliner = JsInliner(config, functions, accessors, inverseNameBindings, functionReader, trace) val moduleMap = translationResult.importedModuleList.associate { it.internalName to it }
for (statement in importStatements) { val inliner = JsInliner(config, functions, accessors, inverseNameBindings, functionReader, trace, moduleMap)
inliner.processImportStatement(statement) for (fragment in translationResult.newFragments) {
inliner.process(fragment, inverseNameBindings(fragment).entries.associateTo(mutableMapOf()) { (name, tag) -> tag to name })
} }
val moduleMap = fillModuleMap(buildModuleMap(fragments), fragmentsToProcess) for (fragment in translationResult.newFragments) {
for (fragment in fragmentsToProcess) {
inliner.existingImports.clear()
inliner.additionalNameBindings.clear()
inliner.inlinedModuleAliases.clear()
inliner.existingNameBindings = collectNameBindings(listOf(fragment))
inliner.acceptStatement<JsGlobalBlock>(fragment.declarationBlock)
// Mostly for the sake of post-processor
// TODO are inline function marked with @Test possible?
if (fragment.tests != null) {
inliner.acceptStatement(fragment.tests)
}
// There can be inlined function in top-level initializers, we need to optimize them as well
val fakeInitFunction = JsFunction(JsDynamicScope, fragment.initializerBlock, "")
val initWrapper = JsGlobalBlock()
initWrapper.statements.add(JsExpressionStatement(fakeInitFunction))
inliner.accept(initWrapper)
initWrapper.statements.removeAt(initWrapper.statements.size - 1)
fragment.initializerBlock.getStatements().addAll(0, initWrapper.statements)
fragment.nameBindings.addAll(inliner.additionalNameBindings)
inliner.addInlinedModules(fragment, moduleMap)
}
for (fragment in fragmentsToProcess) {
val block = JsBlock(fragment.declarationBlock, fragment.initializerBlock, fragment.exportBlock) val block = JsBlock(fragment.declarationBlock, fragment.initializerBlock, fragment.exportBlock)
removeUnusedImports(block) removeUnusedImports(block)
simplifyWrappedFunctions(block) simplifyWrappedFunctions(block)
@@ -625,29 +533,16 @@ class JsInliner private constructor(
} }
} }
private fun buildModuleMap(fragments: List<JsProgramFragment>): MutableMap<JsName, JsImportedModule> { private fun inverseNameBindings(vararg fragments: JsProgramFragment): MutableMap<JsName, String> {
return fillModuleMap(HashMap(), fragments) val name2Tag = mutableMapOf<JsName, String>()
} fragments.forEach {
it.nameBindings.forEach { (tag, name) ->
private fun fillModuleMap( name2Tag[name] = tag
map: MutableMap<JsName, JsImportedModule>,
fragments: List<JsProgramFragment>
): MutableMap<JsName, JsImportedModule> {
for (fragment in fragments) {
for (module in fragment.importedModules) {
map[module.internalName] = module
} }
} }
return map
return name2Tag
} }
private fun isSuspendWithCurrentContinuation(
descriptor: DeclarationDescriptor?,
languageVersionSettings: LanguageVersionSettings
): Boolean {
return (descriptor as? FunctionDescriptor)?.original?.isBuiltInSuspendCoroutineUninterceptedOrReturn(
languageVersionSettings
) ?: false
}
} }
} }
@@ -21,13 +21,6 @@ import org.jetbrains.kotlin.utils.addIfNotNull
import java.util.* import java.util.*
object LabeledBlockToDoWhileTransformation { object LabeledBlockToDoWhileTransformation {
fun apply(fragments: List<JsProgramFragment>) {
for (fragment in fragments) {
apply(fragment.declarationBlock)
apply(fragment.initializerBlock)
}
}
fun apply(root: JsNode) { fun apply(root: JsNode) {
object : JsVisitorWithContextImpl() { object : JsVisitorWithContextImpl() {
val loopOrSwitchStack = Stack<JsStatement>() val loopOrSwitchStack = Stack<JsStatement>()
@@ -120,3 +113,10 @@ object LabeledBlockToDoWhileTransformation {
}.accept(loopOrSwitch) }.accept(loopOrSwitch)
} }
} }
fun transformLabeledBlockToDoWhile(fragments: List<JsProgramFragment>) {
for (fragment in fragments) {
LabeledBlockToDoWhileTransformation.apply(fragment.declarationBlock)
LabeledBlockToDoWhileTransformation.apply(fragment.initializerBlock)
}
}
@@ -19,12 +19,6 @@ package org.jetbrains.kotlin.js.inline.context
import org.jetbrains.kotlin.js.backend.ast.JsContext import org.jetbrains.kotlin.js.backend.ast.JsContext
import org.jetbrains.kotlin.js.backend.ast.JsStatement import org.jetbrains.kotlin.js.backend.ast.JsStatement
interface InliningContext { class InliningContext(val statementContext: JsContext<JsStatement>) {
val statementContext: JsContext<JsStatement> fun newNamingContext() = NamingContext(statementContext)
val statementContextBeforeCurrentFunction: JsContext<JsStatement>
val functionContext: FunctionContext
fun newNamingContext(): NamingContext
} }
@@ -230,16 +230,6 @@ fun collectAccessors(fragments: List<JsProgramFragment>): Map<String, FunctionWi
return result return result
} }
fun collectNameBindings(fragments: List<JsProgramFragment>): MutableMap<JsName, String> {
val result = mutableMapOf<JsName, String>()
for (fragment in fragments) {
for (binding in fragment.nameBindings) {
result[binding.name] = binding.key
}
}
return result
}
fun extractFunction(expression: JsExpression) = when (expression) { fun extractFunction(expression: JsExpression) = when (expression) {
is JsFunction -> FunctionWithWrapper(expression, null) is JsFunction -> FunctionWithWrapper(expression, null)
else -> InlineMetadata.decompose(expression)?.function ?: InlineMetadata.tryExtractFunction(expression) else -> InlineMetadata.decompose(expression)?.function ?: InlineMetadata.tryExtractFunction(expression)
@@ -37,7 +37,7 @@ class JsAstSerializer(private val pathResolver: (File) -> String) {
private val importedNames = mutableSetOf<JsName>() private val importedNames = mutableSetOf<JsName>()
fun serialize(fragment: JsProgramFragment, output: OutputStream) { fun serialize(fragment: JsProgramFragment, output: OutputStream) {
val namesBySignature = fragment.nameBindings.associate { it.key to it.name } val namesBySignature = fragment.nameBindings.associateTo(mutableMapOf()) { it.key to it.name }
importedNames.clear() importedNames.clear()
importedNames += fragment.imports.map { namesBySignature[it.key]!! } importedNames += fragment.imports.map { namesBySignature[it.key]!! }
serialize(fragment).writeTo(output) serialize(fragment).writeTo(output)
@@ -19,7 +19,7 @@ package org.jetbrains.kotlin.js.facade
import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VfsUtilCore
import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.* import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS
import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult
import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.js.config.JSConfigurationKeys
@@ -43,6 +43,11 @@ import java.io.IOException
import java.util.ArrayList import java.util.ArrayList
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils.hasError import org.jetbrains.kotlin.diagnostics.DiagnosticUtils.hasError
import org.jetbrains.kotlin.js.backend.ast.JsGlobalBlock
import org.jetbrains.kotlin.js.backend.ast.JsProgramFragment
import org.jetbrains.kotlin.js.coroutine.transformCoroutines
import org.jetbrains.kotlin.js.translate.general.AstGenerationResult
import org.jetbrains.kotlin.resolve.BindingTrace
/** /**
* An entry point of translator. * An entry point of translator.
@@ -74,7 +79,6 @@ class K2JSTranslator(private val config: JsConfig) {
mainCallParameters: MainCallParameters, mainCallParameters: MainCallParameters,
analysisResult: JsAnalysisResult? = null analysisResult: JsAnalysisResult? = null
): TranslationResult { ): TranslationResult {
var analysisResult = analysisResult
val files = ArrayList<KtFile>() val files = ArrayList<KtFile>()
for (unit in units) { for (unit in units) {
if (unit is TranslationUnit.SourceFile) { if (unit is TranslationUnit.SourceFile) {
@@ -82,46 +86,93 @@ class K2JSTranslator(private val config: JsConfig) {
} }
} }
if (analysisResult == null) { val actualAnalysisResult = analysisResult ?: TopDownAnalyzerFacadeForJS.analyzeFiles(files, config)
analysisResult = TopDownAnalyzerFacadeForJS.analyzeFiles(files, config)
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
}
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
return translate(reporter, files, units, mainCallParameters, actualAnalysisResult)
}
@Throws(TranslationException::class)
private fun translate(
reporter: JsConfig.Reporter,
files: List<KtFile>,
allUnits: List<TranslationUnit>,
mainCallParameters: MainCallParameters,
analysisResult: JsAnalysisResult
): TranslationResult {
val bindingTrace = analysisResult.bindingTrace val bindingTrace = analysisResult.bindingTrace
TopDownAnalyzerFacadeForJS.checkForErrors(files, bindingTrace.bindingContext) TopDownAnalyzerFacadeForJS.checkForErrors(files, bindingTrace.bindingContext)
val moduleDescriptor = analysisResult.moduleDescriptor val moduleDescriptor = analysisResult.moduleDescriptor
val diagnostics = bindingTrace.bindingContext.diagnostics val diagnostics = bindingTrace.bindingContext.diagnostics
val pathResolver = SourceFilePathResolver.create(config) val pathResolver = SourceFilePathResolver.create(config)
val translationResult = Translation.generateAst( val translationResult = Translation.generateAst(bindingTrace, allUnits, mainCallParameters, moduleDescriptor, config, pathResolver)
bindingTrace, units, mainCallParameters, moduleDescriptor, config, pathResolver
)
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
if (hasError(diagnostics)) return TranslationResult.Fail(diagnostics) if (hasError(diagnostics)) return TranslationResult.Fail(diagnostics)
checkCanceled()
val newFragments = ArrayList(translationResult.newFragments)
val allFragments = ArrayList(translationResult.fragments)
JsInliner.process( JsInliner.process(
reporter, config, analysisResult.bindingTrace, translationResult.innerModuleName, reporter,
allFragments, newFragments, translationResult.importStatements config,
analysisResult.bindingTrace,
translationResult
) )
LabeledBlockToDoWhileTransformation.apply(newFragments)
val coroutineTransformer = CoroutineTransformer()
for (fragment in newFragments) {
coroutineTransformer.accept(fragment.declarationBlock)
coroutineTransformer.accept(fragment.initializerBlock)
}
removeUnusedImports(translationResult.program)
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
if (hasError(diagnostics)) return TranslationResult.Fail(diagnostics) if (hasError(diagnostics)) return TranslationResult.Fail(diagnostics)
checkCanceled()
expandIsCalls(newFragments) transformLabeledBlockToDoWhile(translationResult.newFragments)
checkCanceled()
transformCoroutines(translationResult.newFragments)
checkCanceled()
expandIsCalls(translationResult.newFragments)
checkCanceled()
trySaveIncrementalData(files, translationResult, pathResolver, bindingTrace, moduleDescriptor)
checkCanceled()
// Global phases
val program = translationResult.buildProgram()
removeUnusedImports(program)
checkCanceled()
removeDuplicateImports(program)
checkCanceled()
program.resolveTemporaryNames()
checkCanceled()
return if (hasError(diagnostics)) {
TranslationResult.Fail(diagnostics)
} else {
TranslationResult.Success(
config,
files,
program,
diagnostics,
translationResult.importedModuleList.map { it.externalName },
moduleDescriptor,
bindingTrace.bindingContext
)
}
}
private fun checkCanceled() {
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled() ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
}
private fun trySaveIncrementalData(
files: List<KtFile>,
translationResult: AstGenerationResult,
pathResolver: SourceFilePathResolver,
bindingTrace: BindingTrace,
moduleDescriptor: ModuleDescriptor
) {
if (incrementalResults == null) return
val serializer = JsAstSerializer { file -> val serializer = JsAstSerializer { file ->
try { try {
pathResolver.getPathRelativeToSourceRoots(file) pathResolver.getPathRelativeToSourceRoots(file)
@@ -130,45 +181,25 @@ class K2JSTranslator(private val config: JsConfig) {
} }
} }
if (incrementalResults != null) { for (file in files) {
val serializationUtil = KotlinJavascriptSerializationUtil val fragment = translationResult.fragmentMap[file] ?: error("Could not find AST for file: $file")
val output = ByteArrayOutputStream()
serializer.serialize(fragment, output)
val binaryAst = output.toByteArray()
for (file in files) { val scope = translationResult.fileMemberScopes[file] ?: error("Could not find descriptors for file: $file")
val fragment = translationResult.fragmentMap[file] ?: error("Could not find AST for file: $file") val metadataVersion = config.configuration.get(CommonConfigurationKeys.METADATA_VERSION)
val output = ByteArrayOutputStream() val packagePart = KotlinJavascriptSerializationUtil.serializeDescriptors(
serializer.serialize(fragment, output) bindingTrace.bindingContext, moduleDescriptor, scope, file.packageFqName,
val binaryAst = output.toByteArray() config.configuration.languageVersionSettings,
metadataVersion ?: JsMetadataVersion.INSTANCE
)
val scope = translationResult.fileMemberScopes[file] ?: error("Could not find descriptors for file: $file") val ioFile = VfsUtilCore.virtualToIoFile(file.virtualFile)
val metadataVersion = config.configuration.get(CommonConfigurationKeys.METADATA_VERSION) incrementalResults.processPackagePart(ioFile, packagePart.toByteArray(), binaryAst)
val packagePart = serializationUtil.serializeDescriptors(
bindingTrace.bindingContext, moduleDescriptor, scope, file.packageFqName,
config.configuration.languageVersionSettings,
metadataVersion ?: JsMetadataVersion.INSTANCE
)
val ioFile = VfsUtilCore.virtualToIoFile(file.virtualFile)
incrementalResults.processPackagePart(ioFile, packagePart.toByteArray(), binaryAst)
}
val settings = config.configuration.languageVersionSettings
incrementalResults.processHeader(serializationUtil.serializeHeader(moduleDescriptor, null, settings).toByteArray())
} }
removeDuplicateImports(translationResult.program) val settings = config.configuration.languageVersionSettings
translationResult.program.resolveTemporaryNames() incrementalResults.processHeader(KotlinJavascriptSerializationUtil.serializeHeader(moduleDescriptor, null, settings).toByteArray())
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
if (hasError(diagnostics)) return TranslationResult.Fail(diagnostics)
val importedModules = ArrayList<String>()
for (module in translationResult.importedModuleList) {
importedModules.add(module.externalName)
}
return TranslationResult.Success(
config, files, translationResult.program, diagnostics, importedModules,
moduleDescriptor, bindingTrace.bindingContext
)
} }
} }
@@ -17,16 +17,30 @@
package org.jetbrains.kotlin.js.translate.general package org.jetbrains.kotlin.js.translate.general
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.serialization.js.ModuleKind
class AstGenerationResult( class AstGenerationResult(
val program: JsProgram, val fragments: List<JsProgramFragment>,
val innerModuleName: JsName, val fragmentMap: Map<KtFile, JsProgramFragment>,
val fragments: List<JsProgramFragment>, val newFragments: List<JsProgramFragment>,
val fragmentMap: Map<KtFile, JsProgramFragment>, val fileMemberScopes: Map<KtFile, List<DeclarationDescriptor>>,
val newFragments: List<JsProgramFragment>, private val program: JsProgram,
val importStatements: List<JsStatement>, private val moduleDescriptor: ModuleDescriptor,
val fileMemberScopes: Map<KtFile, List<DeclarationDescriptor>>, private val moduleId: String,
val importedModuleList: List<JsImportedModule> private val moduleKind: ModuleKind
) ) {
val innerModuleName = program.getScope().declareName("_");
val importedModuleList: List<JsImportedModule>
get() = fragments.flatMap { it.importedModules }
fun buildProgram(): JsProgram {
val merger = Merger(program, innerModuleName, moduleDescriptor, moduleId, moduleKind)
fragments.forEach { merger.addFragment(it) }
return merger.buildProgram()
}
}
@@ -26,12 +26,24 @@ import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.createPrototypeStatements import org.jetbrains.kotlin.js.translate.utils.createPrototypeStatements
import org.jetbrains.kotlin.js.translate.utils.definePackageAlias import org.jetbrains.kotlin.js.translate.utils.definePackageAlias
import org.jetbrains.kotlin.serialization.js.ModuleKind
class Merger(
val program: JsProgram,
val internalModuleName: JsName,
val module: ModuleDescriptor,
val moduleId: String,
val moduleKind: ModuleKind
) {
private val rootFunction = JsFunction(program.rootScope, JsBlock(), "root function").also {
it.parameters.add(JsParameter(internalModuleName))
}
class Merger(private val rootFunction: JsFunction, val internalModuleName: JsName, val module: ModuleDescriptor) {
// Maps unique signature (see generateSignature) to names // Maps unique signature (see generateSignature) to names
private val nameTable = mutableMapOf<String, JsName>() private val nameTable = mutableMapOf<String, JsName>()
private val importedModuleTable = mutableMapOf<JsImportedModuleKey, JsName>() private val importedModuleTable = mutableMapOf<JsImportedModuleKey, JsName>()
val importBlock = JsGlobalBlock() private val importBlock = JsGlobalBlock()
private val declarationBlock = JsGlobalBlock() private val declarationBlock = JsGlobalBlock()
private val initializerBlock = JsGlobalBlock() private val initializerBlock = JsGlobalBlock()
private val testsMap = mutableMapOf<String, JsStatement>() private val testsMap = mutableMapOf<String, JsStatement>()
@@ -43,25 +55,11 @@ class Merger(private val rootFunction: JsFunction, val internalModuleName: JsNam
private val exportedPackages = mutableMapOf<String, JsName>() private val exportedPackages = mutableMapOf<String, JsName>()
private val exportedTags = mutableSetOf<String>() private val exportedTags = mutableSetOf<String>()
private val fragments = mutableListOf<JsProgramFragment>()
// Add declaration and initialization statements from program fragment to resulting single program // Add declaration and initialization statements from program fragment to resulting single program
fun addFragment(fragment: JsProgramFragment) { fun addFragment(fragment: JsProgramFragment) {
val nameMap = buildNameMap(fragment) fragments.add(fragment)
nameMap.rename(fragment)
for ((key, importExpr) in fragment.imports) {
if (declaredImports.add(key)) {
val name = nameTable[key]!!
importBlock.statements += JsAstUtils.newVar(name, importExpr)
}
}
declarationBlock.statements += fragment.declarationBlock
initializerBlock.statements += fragment.initializerBlock
fragment.tryUpdateTests()
fragment.tryUpdateMain()
addExportStatements(fragment)
classes += fragment.classes
} }
private fun JsProgramFragment.tryUpdateTests() { private fun JsProgramFragment.tryUpdateTests() {
@@ -205,8 +203,32 @@ class Merger(private val rootFunction: JsFunction, val internalModuleName: JsNam
return rootNode return rootNode
} }
private fun mergeNames() {
for (fragment in fragments) {
val nameMap = buildNameMap(fragment)
nameMap.rename(fragment)
for ((key, importExpr) in fragment.imports) {
if (declaredImports.add(key)) {
val name = nameTable[key]!!
importBlock.statements += JsAstUtils.newVar(name, importExpr)
}
}
declarationBlock.statements += fragment.declarationBlock
initializerBlock.statements += fragment.initializerBlock
fragment.tryUpdateTests()
fragment.tryUpdateMain()
addExportStatements(fragment)
classes += fragment.classes
}
}
// Adds different boilerplate code (like imports, class prototypes, etc) to resulting program. // Adds different boilerplate code (like imports, class prototypes, etc) to resulting program.
fun merge() { fun merge() {
mergeNames()
rootFunction.body.statements.apply { rootFunction.body.statements.apply {
addImportForInlineDeclarationIfNecessary() addImportForInlineDeclarationIfNecessary()
this += importBlock.statements this += importBlock.statements
@@ -220,6 +242,37 @@ class Merger(private val rootFunction: JsFunction, val internalModuleName: JsNam
} }
} }
fun buildProgram(): JsProgram {
merge()
val rootBlock = rootFunction.getBody()
val statements = rootBlock.getStatements()
statements.add(0, JsStringLiteral("use strict").makeStmt())
if (!isBuiltinModule(fragments)) {
defineModule(program, statements, moduleId)
}
// Invoke function passing modules as arguments
// This should help minifier tool to recognize references to these modules as local variables and make them shorter.
for (importedModule in importedModules) {
rootFunction.parameters.add(JsParameter(importedModule.internalName))
}
statements.add(JsReturn(internalModuleName.makeRef()))
val block = program.globalBlock
block.statements.addAll(
ModuleWrapperTranslation.wrapIfNecessary(
moduleId, rootFunction, importedModules, program,
moduleKind
)
)
return program
}
private fun MutableList<JsStatement>.addImportForInlineDeclarationIfNecessary() { private fun MutableList<JsStatement>.addImportForInlineDeclarationIfNecessary() {
val importsForInlineName = nameTable[Namer.IMPORTS_FOR_INLINE_PROPERTY] ?: return val importsForInlineName = nameTable[Namer.IMPORTS_FOR_INLINE_PROPERTY] ?: return
this += definePackageAlias( this += definePackageAlias(
@@ -267,4 +320,34 @@ class Merger(private val rootFunction: JsFunction, val internalModuleName: JsNam
cls.interfaces.forEach { addClassPostDeclarations(it, visited, statements) } cls.interfaces.forEach { addClassPostDeclarations(it, visited, statements) }
statements += cls.postDeclarationBlock.statements statements += cls.postDeclarationBlock.statements
} }
companion object {
private val ENUM_SIGNATURE = "kotlin\$Enum"
// TODO is there no better way?
private fun isBuiltinModule(fragments: List<JsProgramFragment>): Boolean {
for (fragment in fragments) {
for (nameBinding in fragment.nameBindings) {
if (nameBinding.key == ENUM_SIGNATURE && !fragment.imports.containsKey(ENUM_SIGNATURE)) {
return true
}
}
}
return false
}
private fun defineModule(program: JsProgram, statements: MutableList<JsStatement>, moduleId: String) {
val rootPackageName = program.scope.findName(Namer.getRootPackageName())
if (rootPackageName != null) {
val namer = Namer.newInstance(program.scope)
statements.add(
JsInvocation(
namer.kotlin("defineModule"),
JsStringLiteral(moduleId),
rootPackageName.makeRef()
).makeStmt()
)
}
}
}
} }
@@ -324,10 +324,8 @@ public final class Translation {
@NotNull JsConfig config, @NotNull JsConfig config,
@NotNull SourceFilePathResolver sourceFilePathResolver @NotNull SourceFilePathResolver sourceFilePathResolver
) { ) {
JsProgram program = new JsProgram(); JsProgram program = new JsProgram();
JsFunction rootFunction = new JsFunction(program.getRootScope(), new JsBlock(), "root function");
JsName internalModuleName = program.getScope().declareName("_");
Merger merger = new Merger(rootFunction, internalModuleName, moduleDescriptor);
Map<KtFile, JsProgramFragment> fragmentMap = new HashMap<>(); Map<KtFile, JsProgramFragment> fragmentMap = new HashMap<>();
List<JsProgramFragment> fragments = new ArrayList<>(); List<JsProgramFragment> fragments = new ArrayList<>();
@@ -354,56 +352,22 @@ public final class Translation {
newFragments.add(fragment); newFragments.add(fragment);
fragmentMap.put(file, fragment); fragmentMap.put(file, fragment);
fileMemberScopes.put(file, fileMemberScope); fileMemberScopes.put(file, fileMemberScope);
merger.addFragment(fragment);
} }
else if (unit instanceof TranslationUnit.BinaryAst) { else if (unit instanceof TranslationUnit.BinaryAst) {
byte[] astData = ((TranslationUnit.BinaryAst) unit).getData(); byte[] astData = ((TranslationUnit.BinaryAst) unit).getData();
JsProgramFragment fragment = deserializer.deserialize(new ByteArrayInputStream(astData)); JsProgramFragment fragment = deserializer.deserialize(new ByteArrayInputStream(astData));
merger.addFragment(fragment);
fragments.add(fragment); fragments.add(fragment);
} }
} }
rootFunction.getParameters().add(new JsParameter(internalModuleName)); return new AstGenerationResult(fragments,
fragmentMap,
merger.merge(); newFragments,
fileMemberScopes,
JsBlock rootBlock = rootFunction.getBody(); program,
moduleDescriptor,
List<JsStatement> statements = rootBlock.getStatements(); config.getModuleId(),
config.getModuleKind());
statements.add(0, new JsStringLiteral("use strict").makeStmt());
if (!isBuiltinModule(fragments)) {
defineModule(program, statements, config.getModuleId());
}
// Invoke function passing modules as arguments
// This should help minifier tool to recognize references to these modules as local variables and make them shorter.
List<JsImportedModule> importedModuleList = merger.getImportedModules();
for (JsImportedModule importedModule : importedModuleList) {
rootFunction.getParameters().add(new JsParameter(importedModule.getInternalName()));
}
statements.add(new JsReturn(internalModuleName.makeRef()));
JsBlock block = program.getGlobalBlock();
block.getStatements().addAll(wrapIfNecessary(config.getModuleId(), rootFunction, importedModuleList, program,
config.getModuleKind()));
return new AstGenerationResult(program, internalModuleName, fragments, fragmentMap, newFragments,
merger.getImportBlock().getStatements(), fileMemberScopes, importedModuleList);
}
private static boolean isBuiltinModule(@NotNull List<JsProgramFragment> fragments) {
for (JsProgramFragment fragment : fragments) {
for (JsNameBinding nameBinding : fragment.getNameBindings()) {
if (nameBinding.getKey().equals(ENUM_SIGNATURE) && !fragment.getImports().containsKey(ENUM_SIGNATURE)) {
return true;
}
}
}
return false;
} }
private static void translateFile( private static void translateFile(
@@ -430,15 +394,6 @@ public final class Translation {
} }
} }
private static void defineModule(@NotNull JsProgram program, @NotNull List<JsStatement> statements, @NotNull String moduleId) {
JsName rootPackageName = program.getScope().findName(Namer.getRootPackageName());
if (rootPackageName != null) {
Namer namer = Namer.newInstance(program.getScope());
statements.add(new JsInvocation(namer.kotlin("defineModule"), new JsStringLiteral(moduleId),
rootPackageName.makeRef()).makeStmt());
}
}
@Nullable @Nullable
private static JsStatement mayBeGenerateTests( private static JsStatement mayBeGenerateTests(
@NotNull TranslationContext context, @NotNull TranslationContext context,
@@ -1,6 +1,5 @@
// KJS_WITH_FULL_RUNTIME // KJS_WITH_FULL_RUNTIME
// EXPECTED_REACHABLE_NODES: 1625 // EXPECTED_REACHABLE_NODES: 1625
// MODULE: lib
// FILE: lib.kt // FILE: lib.kt
@@ -8,8 +7,8 @@ inline fun tenUInt() = 10U
inline fun tenULong() = 10UL inline fun tenULong() = 10UL
// MODULE: main(lib)
// FILE: main.kt // FILE: main.kt
// RECOMPILE
fun box(): String { fun box(): String {