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
@@ -19,7 +19,7 @@ package org.jetbrains.kotlin.js.facade
import com.intellij.openapi.vfs.VfsUtilCore
import org.jetbrains.kotlin.config.CommonConfigurationKeys
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.analyzer.JsAnalysisResult
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
@@ -43,6 +43,11 @@ import java.io.IOException
import java.util.ArrayList
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.
@@ -74,7 +79,6 @@ class K2JSTranslator(private val config: JsConfig) {
mainCallParameters: MainCallParameters,
analysisResult: JsAnalysisResult? = null
): TranslationResult {
var analysisResult = analysisResult
val files = ArrayList<KtFile>()
for (unit in units) {
if (unit is TranslationUnit.SourceFile) {
@@ -82,46 +86,93 @@ class K2JSTranslator(private val config: JsConfig) {
}
}
if (analysisResult == null) {
analysisResult = TopDownAnalyzerFacadeForJS.analyzeFiles(files, config)
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
}
val actualAnalysisResult = analysisResult ?: TopDownAnalyzerFacadeForJS.analyzeFiles(files, config)
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
TopDownAnalyzerFacadeForJS.checkForErrors(files, bindingTrace.bindingContext)
val moduleDescriptor = analysisResult.moduleDescriptor
val diagnostics = bindingTrace.bindingContext.diagnostics
val pathResolver = SourceFilePathResolver.create(config)
val translationResult = Translation.generateAst(
bindingTrace, units, mainCallParameters, moduleDescriptor, config, pathResolver
)
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
val translationResult = Translation.generateAst(bindingTrace, allUnits, mainCallParameters, moduleDescriptor, config, pathResolver)
if (hasError(diagnostics)) return TranslationResult.Fail(diagnostics)
val newFragments = ArrayList(translationResult.newFragments)
val allFragments = ArrayList(translationResult.fragments)
checkCanceled()
JsInliner.process(
reporter, config, analysisResult.bindingTrace, translationResult.innerModuleName,
allFragments, newFragments, translationResult.importStatements
reporter,
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)
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()
}
private fun trySaveIncrementalData(
files: List<KtFile>,
translationResult: AstGenerationResult,
pathResolver: SourceFilePathResolver,
bindingTrace: BindingTrace,
moduleDescriptor: ModuleDescriptor
) {
if (incrementalResults == null) return
val serializer = JsAstSerializer { file ->
try {
pathResolver.getPathRelativeToSourceRoots(file)
@@ -130,45 +181,25 @@ class K2JSTranslator(private val config: JsConfig) {
}
}
if (incrementalResults != null) {
val serializationUtil = KotlinJavascriptSerializationUtil
for (file in files) {
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 fragment = translationResult.fragmentMap[file] ?: error("Could not find AST for file: $file")
val output = ByteArrayOutputStream()
serializer.serialize(fragment, output)
val binaryAst = output.toByteArray()
val scope = translationResult.fileMemberScopes[file] ?: error("Could not find descriptors for file: $file")
val metadataVersion = config.configuration.get(CommonConfigurationKeys.METADATA_VERSION)
val packagePart = KotlinJavascriptSerializationUtil.serializeDescriptors(
bindingTrace.bindingContext, moduleDescriptor, scope, file.packageFqName,
config.configuration.languageVersionSettings,
metadataVersion ?: JsMetadataVersion.INSTANCE
)
val scope = translationResult.fileMemberScopes[file] ?: error("Could not find descriptors for file: $file")
val metadataVersion = config.configuration.get(CommonConfigurationKeys.METADATA_VERSION)
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())
val ioFile = VfsUtilCore.virtualToIoFile(file.virtualFile)
incrementalResults.processPackagePart(ioFile, packagePart.toByteArray(), binaryAst)
}
removeDuplicateImports(translationResult.program)
translationResult.program.resolveTemporaryNames()
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
)
val settings = config.configuration.languageVersionSettings
incrementalResults.processHeader(KotlinJavascriptSerializationUtil.serializeHeader(moduleDescriptor, null, settings).toByteArray())
}
}
@@ -17,16 +17,30 @@
package org.jetbrains.kotlin.js.translate.general
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.serialization.js.ModuleKind
class AstGenerationResult(
val program: JsProgram,
val innerModuleName: JsName,
val fragments: List<JsProgramFragment>,
val fragmentMap: Map<KtFile, JsProgramFragment>,
val newFragments: List<JsProgramFragment>,
val importStatements: List<JsStatement>,
val fileMemberScopes: Map<KtFile, List<DeclarationDescriptor>>,
val importedModuleList: List<JsImportedModule>
)
val fragments: List<JsProgramFragment>,
val fragmentMap: Map<KtFile, JsProgramFragment>,
val newFragments: List<JsProgramFragment>,
val fileMemberScopes: Map<KtFile, List<DeclarationDescriptor>>,
private val program: JsProgram,
private val moduleDescriptor: ModuleDescriptor,
private val moduleId: String,
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.createPrototypeStatements
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
private val nameTable = mutableMapOf<String, JsName>()
private val importedModuleTable = mutableMapOf<JsImportedModuleKey, JsName>()
val importBlock = JsGlobalBlock()
private val importBlock = JsGlobalBlock()
private val declarationBlock = JsGlobalBlock()
private val initializerBlock = JsGlobalBlock()
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 exportedTags = mutableSetOf<String>()
private val fragments = mutableListOf<JsProgramFragment>()
// Add declaration and initialization statements from program fragment to resulting single program
fun addFragment(fragment: JsProgramFragment) {
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
fragments.add(fragment)
}
private fun JsProgramFragment.tryUpdateTests() {
@@ -205,8 +203,32 @@ class Merger(private val rootFunction: JsFunction, val internalModuleName: JsNam
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.
fun merge() {
mergeNames()
rootFunction.body.statements.apply {
addImportForInlineDeclarationIfNecessary()
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() {
val importsForInlineName = nameTable[Namer.IMPORTS_FOR_INLINE_PROPERTY] ?: return
this += definePackageAlias(
@@ -267,4 +320,34 @@ class Merger(private val rootFunction: JsFunction, val internalModuleName: JsNam
cls.interfaces.forEach { addClassPostDeclarations(it, visited, 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 SourceFilePathResolver sourceFilePathResolver
) {
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<>();
List<JsProgramFragment> fragments = new ArrayList<>();
@@ -354,56 +352,22 @@ public final class Translation {
newFragments.add(fragment);
fragmentMap.put(file, fragment);
fileMemberScopes.put(file, fileMemberScope);
merger.addFragment(fragment);
}
else if (unit instanceof TranslationUnit.BinaryAst) {
byte[] astData = ((TranslationUnit.BinaryAst) unit).getData();
JsProgramFragment fragment = deserializer.deserialize(new ByteArrayInputStream(astData));
merger.addFragment(fragment);
fragments.add(fragment);
}
}
rootFunction.getParameters().add(new JsParameter(internalModuleName));
merger.merge();
JsBlock rootBlock = rootFunction.getBody();
List<JsStatement> statements = rootBlock.getStatements();
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;
return new AstGenerationResult(fragments,
fragmentMap,
newFragments,
fileMemberScopes,
program,
moduleDescriptor,
config.getModuleId(),
config.getModuleKind());
}
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
private static JsStatement mayBeGenerateTests(
@NotNull TranslationContext context,
@@ -1,6 +1,5 @@
// KJS_WITH_FULL_RUNTIME
// EXPECTED_REACHABLE_NODES: 1625
// MODULE: lib
// FILE: lib.kt
@@ -8,8 +7,8 @@ inline fun tenUInt() = 10U
inline fun tenULong() = 10UL
// MODULE: main(lib)
// FILE: main.kt
// RECOMPILE
fun box(): String {