[JS IR] Support per-file mode and ES modules
This commit is contained in:
+3
@@ -170,6 +170,9 @@ class K2JSCompilerArguments : CommonCompilerArguments() {
|
||||
@Argument(value = "-Xir-per-module-output-name", description = "Adds a custom output name to the splitted js files")
|
||||
var irPerModuleOutputName: String? by NullableStringFreezableVar(null)
|
||||
|
||||
@Argument(value = "-Xir-per-file", description = "Splits generated .js per-file")
|
||||
var irPerFile: Boolean by FreezableVar(false)
|
||||
|
||||
@Argument(
|
||||
value = "-Xinclude",
|
||||
valueDescription = "<path>",
|
||||
|
||||
+1
@@ -24,6 +24,7 @@ public interface K2JsArgumentConstants {
|
||||
String MODULE_AMD = "amd";
|
||||
String MODULE_COMMONJS = "commonjs";
|
||||
String MODULE_UMD = "umd";
|
||||
String MODULE_ES = "es";
|
||||
|
||||
String SOURCE_MAP_SOURCE_CONTENT_ALWAYS = "always";
|
||||
String SOURCE_MAP_SOURCE_CONTENT_NEVER = "never";
|
||||
|
||||
@@ -44,6 +44,8 @@ import org.jetbrains.kotlin.ir.backend.js.ic.checkCaches
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName
|
||||
import org.jetbrains.kotlin.ir.backend.js.ic.*
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
|
||||
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer
|
||||
import org.jetbrains.kotlin.ir.declarations.persistent.PersistentIrFactory
|
||||
import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult
|
||||
import org.jetbrains.kotlin.js.config.*
|
||||
@@ -332,20 +334,21 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
|
||||
|
||||
val start = System.currentTimeMillis()
|
||||
|
||||
val compiledModule = compile(
|
||||
val granularity = when {
|
||||
arguments.irPerModule -> JsGenerationGranularity.PER_MODULE
|
||||
arguments.irPerFile -> JsGenerationGranularity.PER_FILE
|
||||
else -> JsGenerationGranularity.WHOLE_PROGRAM
|
||||
}
|
||||
|
||||
val ir = compile(
|
||||
module,
|
||||
phaseConfig,
|
||||
if (arguments.irDceDriven) PersistentIrFactory() else IrFactoryImpl,
|
||||
mainArguments = mainCallArguments,
|
||||
generateFullJs = !arguments.irDce,
|
||||
generateDceJs = arguments.irDce,
|
||||
dceRuntimeDiagnostic = RuntimeDiagnostic.resolve(
|
||||
arguments.irDceRuntimeDiagnostic,
|
||||
messageCollector
|
||||
),
|
||||
dceDriven = arguments.irDceDriven,
|
||||
multiModule = arguments.irPerModule,
|
||||
relativeRequirePath = true,
|
||||
propertyLazyInitialization = arguments.irPropertyLazyInitialization,
|
||||
baseClassIntoMetadata = arguments.irBaseClassInMetadata,
|
||||
safeExternalBoolean = arguments.irSafeExternalBoolean,
|
||||
@@ -354,11 +357,28 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
|
||||
messageCollector
|
||||
),
|
||||
lowerPerModule = icCaches.isNotEmpty(),
|
||||
granularity = granularity
|
||||
)
|
||||
|
||||
val transformer = IrModuleToJsTransformer(
|
||||
ir.context,
|
||||
mainCallArguments,
|
||||
fullJs = true,
|
||||
dceJs = arguments.irDce,
|
||||
multiModule = arguments.irPerModule,
|
||||
relativeRequirePath = false
|
||||
)
|
||||
val compiledModule: CompilerResult = transformer.generateModule(ir.allModules)
|
||||
|
||||
messageCollector.report(INFO, "Executable production duration: ${System.currentTimeMillis() - start}ms")
|
||||
|
||||
val outputs = if (arguments.irDce && !arguments.irDceDriven) compiledModule.outputsAfterDce!! else compiledModule.outputs!!
|
||||
|
||||
|
||||
val outputs = if (arguments.irDce && !arguments.irDceDriven)
|
||||
compiledModule.outputsAfterDce!!
|
||||
else
|
||||
compiledModule.outputs!!
|
||||
|
||||
outputFile.write(outputs)
|
||||
outputs.dependencies.forEach { (name, content) ->
|
||||
outputFile.resolveSibling("$name.js").write(content)
|
||||
@@ -495,7 +515,8 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
|
||||
K2JsArgumentConstants.MODULE_PLAIN to ModuleKind.PLAIN,
|
||||
K2JsArgumentConstants.MODULE_COMMONJS to ModuleKind.COMMON_JS,
|
||||
K2JsArgumentConstants.MODULE_AMD to ModuleKind.AMD,
|
||||
K2JsArgumentConstants.MODULE_UMD to ModuleKind.UMD
|
||||
K2JsArgumentConstants.MODULE_UMD to ModuleKind.UMD,
|
||||
K2JsArgumentConstants.MODULE_ES to ModuleKind.ES,
|
||||
)
|
||||
private val sourceMapContentEmbeddingMap = mapOf(
|
||||
K2JsArgumentConstants.SOURCE_MAP_SOURCE_CONTENT_ALWAYS to SourceMapSourceEmbedding.ALWAYS,
|
||||
|
||||
+11
-5
@@ -25,12 +25,18 @@ import org.jetbrains.kotlin.ir.types.isUnit
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||
|
||||
class PropertyAccessorInlineLowering(private val context: CommonBackendContext) : BodyLoweringPass {
|
||||
open class PropertyAccessorInlineLowering(
|
||||
private val context: CommonBackendContext,
|
||||
) : BodyLoweringPass {
|
||||
|
||||
private val IrProperty.isSafeToInline: Boolean get() = isTopLevel || (modality === Modality.FINAL || visibility == DescriptorVisibilities.PRIVATE) || (parent as IrClass).modality === Modality.FINAL
|
||||
fun IrProperty.isSafeToInlineInClosedWorld() =
|
||||
isTopLevel || (modality === Modality.FINAL || visibility == DescriptorVisibilities.PRIVATE) || (parent as IrClass).modality === Modality.FINAL
|
||||
|
||||
open fun IrProperty.isSafeToInline(accessContainer: IrDeclaration): Boolean =
|
||||
isSafeToInlineInClosedWorld()
|
||||
|
||||
// TODO: implement general function inlining optimization and replace it with
|
||||
private inner class AccessorInliner : IrElementTransformerVoid() {
|
||||
private inner class AccessorInliner(val container: IrDeclaration) : IrElementTransformerVoid() {
|
||||
|
||||
private val unitType = context.irBuiltIns.unitType
|
||||
|
||||
@@ -38,7 +44,7 @@ class PropertyAccessorInlineLowering(private val context: CommonBackendContext)
|
||||
val property = callee.correspondingPropertySymbol?.owner ?: return false
|
||||
|
||||
// Some devirtualization required here
|
||||
if (!property.isSafeToInline) return false
|
||||
if (!property.isSafeToInline(container)) return false
|
||||
|
||||
val parent = property.parent
|
||||
if (parent is IrClass) {
|
||||
@@ -176,6 +182,6 @@ class PropertyAccessorInlineLowering(private val context: CommonBackendContext)
|
||||
}
|
||||
|
||||
override fun lower(irBody: IrBody, container: IrDeclaration) {
|
||||
irBody.transformChildrenVoid(AccessorInliner())
|
||||
irBody.transformChildrenVoid(AccessorInliner(container))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.impl.EmptyPackageFragmentDescriptor
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.ir.*
|
||||
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
|
||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
||||
import org.jetbrains.kotlin.ir.backend.js.lower.JsInnerClassesSupport
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.JsInlineClassesUtils
|
||||
@@ -58,7 +59,8 @@ class JsIrBackendContext(
|
||||
val baseClassIntoMetadata: Boolean = false,
|
||||
val safeExternalBoolean: Boolean = false,
|
||||
val safeExternalBooleanDiagnostic: RuntimeDiagnostic? = null,
|
||||
override val mapping: JsMapping = JsMapping(symbolTable.irFactory)
|
||||
override val mapping: JsMapping = JsMapping(symbolTable.irFactory),
|
||||
val granularity: JsGenerationGranularity = JsGenerationGranularity.WHOLE_PROGRAM,
|
||||
) : JsCommonBackendContext {
|
||||
val fileToInitializationFuns: MutableMap<IrFile, IrSimpleFunction?> = mutableMapOf()
|
||||
val fileToInitializerPureness: MutableMap<IrFile, Boolean> = mutableMapOf()
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.backend.common.lower.optimizations.FoldConstantLower
|
||||
import org.jetbrains.kotlin.backend.common.lower.optimizations.PropertyAccessorInlineLowering
|
||||
import org.jetbrains.kotlin.backend.common.phaser.*
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
|
||||
import org.jetbrains.kotlin.ir.backend.js.lower.*
|
||||
import org.jetbrains.kotlin.ir.backend.js.lower.calls.CallsLowering
|
||||
import org.jetbrains.kotlin.ir.backend.js.lower.cleanup.CleanupLowering
|
||||
@@ -397,7 +398,7 @@ private val removeInitializersForLazyProperties = makeDeclarationTransformerPhas
|
||||
)
|
||||
|
||||
private val propertyAccessorInlinerLoweringPhase = makeBodyLoweringPhase(
|
||||
::PropertyAccessorInlineLowering,
|
||||
::JsPropertyAccessorInlineLowering,
|
||||
name = "PropertyAccessorInlineLowering",
|
||||
description = "[Optimization] Inline property accessors"
|
||||
)
|
||||
@@ -775,6 +776,14 @@ private val cleanupLoweringPhase = makeBodyLoweringPhase(
|
||||
name = "CleanupLowering",
|
||||
description = "Clean up IR before codegen"
|
||||
)
|
||||
private val moveOpenClassesToSeparatePlaceLowering = makeCustomJsModulePhase(
|
||||
{ context, module ->
|
||||
if (context.granularity == JsGenerationGranularity.PER_FILE)
|
||||
moveOpenClassesToSeparateFiles(module)
|
||||
},
|
||||
name = "MoveOpenClassesToSeparateFiles",
|
||||
description = "Move open classes to separate files"
|
||||
).toModuleLowering()
|
||||
|
||||
private val jsSuspendArityStorePhase = makeDeclarationTransformerPhase(
|
||||
::JsSuspendArityStoreLowering,
|
||||
@@ -880,6 +889,9 @@ private val loweringList = listOf<Lowering>(
|
||||
captureStackTraceInThrowablesPhase,
|
||||
callsLoweringPhase,
|
||||
cleanupLoweringPhase,
|
||||
// Currently broken due to static members lowering making single-open-class
|
||||
// files non-recognizable as single-class files
|
||||
// moveOpenClassesToSeparatePlaceLowering,
|
||||
validateIrAfterLowering,
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,457 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.ir.backend.js.codegen
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.LoweredIr
|
||||
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity.*
|
||||
import org.jetbrains.kotlin.ir.backend.js.export.*
|
||||
import org.jetbrains.kotlin.ir.backend.js.lower.StaticMembersLowering
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrFileToJsTransformer
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.processClassModels
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.*
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.types.classOrNull
|
||||
import org.jetbrains.kotlin.ir.util.file
|
||||
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
|
||||
import org.jetbrains.kotlin.ir.util.hasInterfaceParent
|
||||
import org.jetbrains.kotlin.ir.util.isInterface
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||
import org.jetbrains.kotlin.js.backend.ast.*
|
||||
import org.jetbrains.kotlin.serialization.js.ModuleKind
|
||||
import kotlin.math.abs
|
||||
|
||||
interface CompilerOutputSink {
|
||||
fun write(module: String, path: String, content: String)
|
||||
}
|
||||
|
||||
class JsGenerationOptions(
|
||||
val jsExtension: String = "js",
|
||||
val generatePackageJson: Boolean = false,
|
||||
val generateTypeScriptDefinitions: Boolean = false,
|
||||
)
|
||||
|
||||
class IrToJs(
|
||||
private val backendContext: JsIrBackendContext,
|
||||
private val guid: (IrDeclaration) -> String,
|
||||
private val outputSink: CompilerOutputSink,
|
||||
private val mainArguments: List<String>?,
|
||||
private val granularity: JsGenerationGranularity,
|
||||
private val mainModuleName: String,
|
||||
private val options: JsGenerationOptions,
|
||||
) {
|
||||
val indexFileName = "index.${options.jsExtension}"
|
||||
|
||||
val FileUnit.initFunctionName
|
||||
get() = "KotlinInit$" + sanitizeName(pathToJsModule(file))
|
||||
|
||||
sealed class CodegenUnitReference
|
||||
object ThisUnitReference : CodegenUnitReference()
|
||||
inner class OtherUnitReference(
|
||||
module: IrModuleFragment,
|
||||
) : CodegenUnitReference() {
|
||||
// Path to entry point of other module from "top-level", e.g. directory which contains all other modules
|
||||
val importPath = "./" + module.jsModuleName + "/" + indexFileName
|
||||
}
|
||||
|
||||
abstract class CodegenUnit {
|
||||
abstract val packageFragments: Iterable<IrPackageFragment>
|
||||
abstract val externalPackageFragments: Iterable<IrPackageFragment>
|
||||
abstract fun referenceCodegenUnitOfDeclaration(declaration: IrDeclaration): CodegenUnitReference
|
||||
abstract val pathToKotlinModulesRoot: String
|
||||
}
|
||||
|
||||
inner class FileUnit(val file: IrFile, val externalFile: IrFile?) : CodegenUnit() {
|
||||
override val packageFragments =
|
||||
listOf(file)
|
||||
|
||||
override val externalPackageFragments =
|
||||
listOfNotNull(externalFile)
|
||||
|
||||
override fun referenceCodegenUnitOfDeclaration(declaration: IrDeclaration): CodegenUnitReference =
|
||||
when (val declarationFile = declaration.file) {
|
||||
file -> ThisUnitReference
|
||||
else -> OtherUnitReference(declarationFile.module)
|
||||
}
|
||||
|
||||
override val pathToKotlinModulesRoot: String by lazy {
|
||||
"../".repeat(file.fqName.pathSegments().size + 1)
|
||||
}
|
||||
}
|
||||
|
||||
inner class ModuleUnit(val module: IrModuleFragment) : CodegenUnit() {
|
||||
override val packageFragments: Iterable<IrPackageFragment> =
|
||||
module.files
|
||||
|
||||
override val externalPackageFragments: Iterable<IrPackageFragment> =
|
||||
packageFragments.mapNotNull { backendContext.externalPackageFragment[it.symbol] }
|
||||
|
||||
override fun referenceCodegenUnitOfDeclaration(declaration: IrDeclaration): CodegenUnitReference =
|
||||
when (val declarationModule = declaration.file.module) {
|
||||
module -> ThisUnitReference
|
||||
else -> OtherUnitReference(declarationModule)
|
||||
}
|
||||
|
||||
override val pathToKotlinModulesRoot: String = "../"
|
||||
}
|
||||
|
||||
class WholeProgramUnit(
|
||||
val modules: Iterable<IrModuleFragment>,
|
||||
val externalModules: Iterable<IrPackageFragment>
|
||||
) : CodegenUnit() {
|
||||
override val packageFragments: Iterable<IrPackageFragment> =
|
||||
modules.flatMap { it.files }
|
||||
|
||||
override val externalPackageFragments: Iterable<IrPackageFragment>
|
||||
get() = externalModules
|
||||
|
||||
override fun referenceCodegenUnitOfDeclaration(declaration: IrDeclaration): CodegenUnitReference =
|
||||
ThisUnitReference
|
||||
|
||||
override val pathToKotlinModulesRoot: String
|
||||
get() = "../"
|
||||
}
|
||||
|
||||
private fun pathToJsModule(file: IrFile): String =
|
||||
"${fileJsRootModuleName(file)}/${fileJsSubModulePath(file)}"
|
||||
|
||||
private fun fileJsRootModuleName(file: IrFile): String =
|
||||
when (granularity) {
|
||||
WHOLE_PROGRAM -> mainModuleName
|
||||
PER_MODULE, PER_FILE -> file.module.jsModuleName
|
||||
}
|
||||
|
||||
private fun fileJsSubModulePath(file: IrFile): String =
|
||||
when (granularity) {
|
||||
WHOLE_PROGRAM, PER_MODULE -> indexFileName
|
||||
|
||||
PER_FILE -> {
|
||||
val maybeSingleOpenClass = (file.declarations.singleOrNull() as? IrClass)?.takeIf {
|
||||
it.modality == Modality.ABSTRACT || it.modality == Modality.OPEN
|
||||
}
|
||||
|
||||
val hash = abs((maybeSingleOpenClass?.let { guid(it) } ?: file.path).hashCode())
|
||||
val filePrefix = maybeSingleOpenClass?.name?.asString()?.let { sanitizeName(it) + ".class" } ?: file.name
|
||||
val fileName = "${filePrefix}_$hash.${options.jsExtension}"
|
||||
val packagePath = file.fqName.pathSegments().joinToString("") { it.identifier + "/" }
|
||||
"$packagePath$fileName"
|
||||
}
|
||||
}
|
||||
|
||||
class GeneratedUnit(
|
||||
val jsStatements: List<JsStatement>,
|
||||
val exportedDeclarations: List<ExportedDeclaration>,
|
||||
)
|
||||
|
||||
fun generateUnit(unit: CodegenUnit): GeneratedUnit {
|
||||
val exportedDeclarations: List<ExportedDeclaration> =
|
||||
with(ExportModelGenerator(backendContext, generateNamespacesForPackages = false)) {
|
||||
(unit.externalPackageFragments + unit.packageFragments).flatMap { packageFragment ->
|
||||
generateExport(packageFragment)
|
||||
}
|
||||
}
|
||||
|
||||
val stableNames: Set<String> = collectStableNames(unit)
|
||||
val nameGenerator = NewNamerImpl(backendContext, unit, guid, stableNames)
|
||||
|
||||
val staticContext = JsStaticContext(
|
||||
backendContext = backendContext,
|
||||
irNamer = nameGenerator,
|
||||
globalNameScope = nameGenerator.staticNames
|
||||
)
|
||||
|
||||
val rootContext = JsGenerationContext(
|
||||
currentFunction = null,
|
||||
currentFile = null,
|
||||
staticContext = staticContext,
|
||||
localNames = LocalNameGenerator(NameTable())
|
||||
)
|
||||
|
||||
|
||||
val declarationStatements: List<JsStatement> = unit.packageFragments.flatMap {
|
||||
StaticMembersLowering(backendContext).lower(it as IrFile)
|
||||
it.accept(IrFileToJsTransformer(), rootContext).statements
|
||||
}
|
||||
|
||||
val preDeclarationBlock = JsGlobalBlock()
|
||||
val postDeclarationBlock = JsGlobalBlock()
|
||||
processClassModels(rootContext.staticContext.classModels, preDeclarationBlock, postDeclarationBlock)
|
||||
|
||||
val statements = mutableListOf<JsStatement>()
|
||||
statements += nameGenerator.internalImports.values
|
||||
statements += preDeclarationBlock
|
||||
statements += declarationStatements
|
||||
statements += postDeclarationBlock
|
||||
|
||||
// Generate module initialization
|
||||
|
||||
val initializerBlock = rootContext.staticContext.initializerBlock
|
||||
when (unit) {
|
||||
is WholeProgramUnit, is ModuleUnit -> {
|
||||
// Run initialization during ES module initialization
|
||||
statements += initializerBlock
|
||||
}
|
||||
|
||||
is FileUnit -> {
|
||||
// Postpone initialization by putting it into a separate function
|
||||
// Will be called later in proper order after class model is initialized
|
||||
val initFunction = JsFunction(emptyScope, JsBlock(initializerBlock.statements), "init fun")
|
||||
initFunction.name = JsName(unit.initFunctionName)
|
||||
statements += initFunction.makeStmt()
|
||||
statements += JsExport(initFunction.name)
|
||||
}
|
||||
}
|
||||
|
||||
// Generate internal export
|
||||
|
||||
val internalExports = mutableListOf<JsExport.Element>()
|
||||
fun export(declaration: IrDeclarationWithName) {
|
||||
internalExports += JsExport.Element(nameGenerator.getNameForStaticDeclaration(declaration), JsName(guid(declaration)))
|
||||
}
|
||||
|
||||
for (fragment in unit.packageFragments) {
|
||||
for (declaration in fragment.declarations) {
|
||||
if (declaration is IrDeclarationWithName) {
|
||||
export(declaration)
|
||||
}
|
||||
|
||||
// Default implementations of interface methods are nested under interface declarations in IR at this point,
|
||||
// but they are effectively used as a static declaration and can be directly referenced by other codegen unit,
|
||||
// thus requiring internal export
|
||||
declaration.acceptChildrenVoid(object : IrElementVisitorVoid {
|
||||
override fun visitElement(element: IrElement) {
|
||||
element.acceptChildrenVoid(this)
|
||||
}
|
||||
|
||||
override fun visitSimpleFunction(declaration: IrSimpleFunction) {
|
||||
if (declaration.hasInterfaceParent() && declaration.body != null) {
|
||||
export(declaration)
|
||||
}
|
||||
super.visitSimpleFunction(declaration)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
statements += JsExport(JsExport.Subject.Elements(internalExports), null)
|
||||
|
||||
// Generate external export
|
||||
|
||||
val globalNames = NameTable<String>(nameGenerator.staticNames)
|
||||
val exporter = ExportModelToJsStatements(
|
||||
nameGenerator,
|
||||
declareNewNamespace = { globalNames.declareFreshName(it, it) }
|
||||
)
|
||||
exportedDeclarations.forEach {
|
||||
statements += exporter.generateDeclarationExport(it, null)
|
||||
}
|
||||
|
||||
return GeneratedUnit(statements, exportedDeclarations)
|
||||
}
|
||||
|
||||
private fun collectStableNames(unit: CodegenUnit): Set<String> {
|
||||
val newStableStaticNamesCollectorVisitor =
|
||||
NewStableStaticNamesCollectorVisitor(needToCollectReferences = granularity != WHOLE_PROGRAM)
|
||||
unit.packageFragments.forEach { it.acceptVoid(newStableStaticNamesCollectorVisitor) }
|
||||
unit.externalPackageFragments.forEach { it.acceptVoid(newStableStaticNamesCollectorVisitor) }
|
||||
|
||||
return newStableStaticNamesCollectorVisitor.collectedStableNames
|
||||
}
|
||||
|
||||
// Returns import statement and call expression
|
||||
private fun invokeFunctionFromEntryJsFile(
|
||||
function: IrFunction,
|
||||
args: List<JsExpression> = emptyList()
|
||||
): Pair<JsStatement, JsExpression> {
|
||||
val name = guid(function)
|
||||
val importPath = if (granularity == WHOLE_PROGRAM) "./$indexFileName" else "../" + pathToJsModule(function.file)
|
||||
return Pair(
|
||||
JsImport(importPath, mutableListOf(JsImport.Element(name, null))),
|
||||
JsInvocation(JsNameRef(name), args)
|
||||
)
|
||||
}
|
||||
|
||||
private fun invokeFunctionFromEntryJsFileAsStatements(
|
||||
function: IrFunction,
|
||||
args: List<JsExpression> = emptyList()
|
||||
): List<JsStatement> =
|
||||
invokeFunctionFromEntryJsFile(function, args)
|
||||
.let { listOf(it.first, it.second.makeStmt()) }
|
||||
|
||||
fun generateModules(
|
||||
mainModule: IrModuleFragment,
|
||||
allModules: List<IrModuleFragment>
|
||||
) {
|
||||
when (granularity) {
|
||||
WHOLE_PROGRAM ->
|
||||
generateModule(mainModule, allModules)
|
||||
|
||||
PER_MODULE,
|
||||
PER_FILE ->
|
||||
allModules.forEach { module ->
|
||||
generateModule(mainModule = module, allModules = emptyList())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun generateModuleLevelCode(module: IrModuleFragment, statements: MutableList<JsStatement>) {
|
||||
if (mainArguments != null) {
|
||||
val mainFunction = JsMainFunctionDetector(backendContext).getMainFunctionOrNull(module)
|
||||
if (mainFunction != null) {
|
||||
val generateArgv = mainFunction.valueParameters.firstOrNull()?.isStringArrayParameter() ?: false
|
||||
val generateContinuation = mainFunction.isLoweredSuspendFunction(backendContext)
|
||||
|
||||
val mainArgumentsArray =
|
||||
if (generateArgv)
|
||||
JsArrayLiteral(mainArguments.map { JsStringLiteral(it) })
|
||||
else
|
||||
null
|
||||
|
||||
val continuation =
|
||||
if (generateContinuation) {
|
||||
val (import, invoke) = invokeFunctionFromEntryJsFile(backendContext.coroutineEmptyContinuation.owner.getter!!)
|
||||
statements += import
|
||||
invoke
|
||||
} else
|
||||
null
|
||||
|
||||
statements += invokeFunctionFromEntryJsFileAsStatements(
|
||||
mainFunction, listOfNotNull(mainArgumentsArray, continuation)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
backendContext.testRoots[module]?.let { testContainer ->
|
||||
statements += invokeFunctionFromEntryJsFileAsStatements(testContainer)
|
||||
}
|
||||
}
|
||||
|
||||
fun generateModule(
|
||||
mainModule: IrModuleFragment,
|
||||
allModules: List<IrModuleFragment>,
|
||||
) {
|
||||
val moduleName = mainModule.jsModuleName
|
||||
val indexJsStatements = mutableListOf<JsStatement>()
|
||||
val exportedDeclarations = mutableListOf<ExportedDeclaration>()
|
||||
|
||||
when (granularity) {
|
||||
PER_FILE -> {
|
||||
for (file in mainModule.files.sortedBy(::fileInitOrder)) {
|
||||
if (file.declarations.isEmpty()) continue
|
||||
|
||||
val pathToSubModule = fileJsSubModulePath(file)
|
||||
indexJsStatements += JsExport(JsExport.Subject.All, fromModule = "./$pathToSubModule")
|
||||
|
||||
val unit = FileUnit(file, backendContext.externalPackageFragment[file.symbol])
|
||||
val generatedUnit = generateUnit(unit)
|
||||
|
||||
val importElements = JsImport.Element(unit.initFunctionName, null)
|
||||
indexJsStatements += JsImport("./$pathToSubModule", mutableListOf(importElements))
|
||||
indexJsStatements += JsInvocation(JsNameRef(JsName(unit.initFunctionName))).makeStmt()
|
||||
|
||||
exportedDeclarations += generatedUnit.exportedDeclarations
|
||||
|
||||
outputSink.write(
|
||||
file.module.jsModuleName,
|
||||
pathToSubModule,
|
||||
"// Kotlin file: ${file.path}\n" + generatedUnit.jsStatements.toJsCodeString()
|
||||
)
|
||||
}
|
||||
generateModuleLevelCode(mainModule, indexJsStatements)
|
||||
}
|
||||
|
||||
PER_MODULE -> {
|
||||
val generatedUnit = generateUnit(ModuleUnit(mainModule))
|
||||
indexJsStatements += generatedUnit.jsStatements
|
||||
generateModuleLevelCode(mainModule, indexJsStatements)
|
||||
exportedDeclarations += generatedUnit.exportedDeclarations
|
||||
}
|
||||
|
||||
WHOLE_PROGRAM -> {
|
||||
val generatedUnit = generateUnit(WholeProgramUnit(allModules, backendContext.externalPackageFragment.values))
|
||||
indexJsStatements += generatedUnit.jsStatements
|
||||
allModules.forEach {
|
||||
generateModuleLevelCode(it, indexJsStatements)
|
||||
}
|
||||
exportedDeclarations += generatedUnit.exportedDeclarations
|
||||
}
|
||||
}
|
||||
|
||||
outputSink.write(moduleName, indexFileName, indexJsStatements.toJsCodeString())
|
||||
|
||||
if (options.generatePackageJson) {
|
||||
outputSink.write(moduleName, "package.json", """{ "main": "$indexFileName", "type": "module" }""")
|
||||
}
|
||||
|
||||
if (options.generateTypeScriptDefinitions && exportedDeclarations.isNotEmpty()) {
|
||||
val dts = ExportedModule(moduleName, moduleKind = ModuleKind.ES, exportedDeclarations).toTypeScript()
|
||||
outputSink.write(moduleName, "index.d.ts", dts)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fileInitOrder(file: IrFile): Int =
|
||||
when (val singleDeclaration = file.declarations.singleOrNull()) {
|
||||
// Initialize parent classes before child classes
|
||||
// TODO: Comment about open classes in separate files
|
||||
is IrClass -> singleDeclaration.getInheritanceChainLength()
|
||||
// Initialize regular files after all open classes
|
||||
else -> Int.MAX_VALUE
|
||||
}
|
||||
|
||||
private fun IrClass.getInheritanceChainLength(): Int {
|
||||
if (symbol == backendContext.irBuiltIns.anyClass)
|
||||
return 0
|
||||
|
||||
// FIXME: Filter out interfaces
|
||||
superTypes.forEach { superType ->
|
||||
val superClass: IrClass? = superType.classOrNull?.owner
|
||||
if (superClass != null && /* !!! */ !superClass.isInterface)
|
||||
return superClass.getInheritanceChainLength() + 1
|
||||
}
|
||||
|
||||
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
private val IrModuleFragment.jsModuleName: String
|
||||
get() = name.asString().dropWhile { it == '<' }.dropLastWhile { it == '>' }
|
||||
|
||||
private fun List<JsStatement>.toJsCodeString(): String =
|
||||
JsGlobalBlock().also { it.statements += this }.toString()
|
||||
|
||||
enum class JsGenerationGranularity {
|
||||
WHOLE_PROGRAM,
|
||||
PER_MODULE,
|
||||
PER_FILE
|
||||
}
|
||||
|
||||
fun generateEsModules(
|
||||
ir: LoweredIr,
|
||||
outputSink: CompilerOutputSink,
|
||||
mainArguments: List<String>?,
|
||||
granularity: JsGenerationGranularity,
|
||||
options: JsGenerationOptions,
|
||||
) {
|
||||
// Declaration numeration to create temporary GUID
|
||||
// TODO: Replace with an actual GUID
|
||||
val numerator = StaticDeclarationNumerator()
|
||||
ir.allModules.forEach { numerator.add(it) }
|
||||
|
||||
fun guid(declaration: IrDeclaration): String {
|
||||
val name = sanitizeName((declaration as IrDeclarationWithName).name.toString())
|
||||
val number = numerator.numeration[declaration]
|
||||
?: error("Can't find number for declaration ${declaration.fqNameWhenAvailable}")
|
||||
// TODO: Use shorter names in release mode
|
||||
return "${name}_GUID_${number}"
|
||||
}
|
||||
|
||||
val ir2js = IrToJs(ir.context, ::guid, outputSink, mainArguments, granularity, ir.mainModule.jsModuleName, options)
|
||||
ir2js.generateModules(ir.mainModule, ir.allModules)
|
||||
}
|
||||
@@ -8,6 +8,9 @@ package org.jetbrains.kotlin.ir.backend.js
|
||||
import org.jetbrains.kotlin.backend.common.lower
|
||||
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
|
||||
import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
|
||||
import org.jetbrains.kotlin.ir.backend.js.ic.SerializedIcData
|
||||
import org.jetbrains.kotlin.ir.backend.js.ic.ModuleCache
|
||||
import org.jetbrains.kotlin.ir.backend.js.ic.icCompile
|
||||
import org.jetbrains.kotlin.ir.backend.js.lower.generateTests
|
||||
@@ -37,39 +40,36 @@ class CompilationOutputs(
|
||||
val dependencies: Iterable<Pair<String, CompilationOutputs>> = emptyList()
|
||||
)
|
||||
|
||||
class LoweredIr(
|
||||
val context: JsIrBackendContext,
|
||||
val mainModule: IrModuleFragment,
|
||||
val allModules: List<IrModuleFragment>
|
||||
)
|
||||
|
||||
fun compile(
|
||||
depsDescriptors: ModulesStructure,
|
||||
phaseConfig: PhaseConfig,
|
||||
irFactory: IrFactory,
|
||||
mainArguments: List<String>?,
|
||||
exportedDeclarations: Set<FqName> = emptySet(),
|
||||
generateFullJs: Boolean = true,
|
||||
generateDceJs: Boolean = false,
|
||||
dceDriven: Boolean = false,
|
||||
dceRuntimeDiagnostic: RuntimeDiagnostic? = null,
|
||||
es6mode: Boolean = false,
|
||||
multiModule: Boolean = false,
|
||||
relativeRequirePath: Boolean = false,
|
||||
propertyLazyInitialization: Boolean,
|
||||
verifySignatures: Boolean = true,
|
||||
baseClassIntoMetadata: Boolean = false,
|
||||
lowerPerModule: Boolean = false,
|
||||
safeExternalBoolean: Boolean = false,
|
||||
safeExternalBooleanDiagnostic: RuntimeDiagnostic? = null,
|
||||
filesToLower: Set<String>? = null
|
||||
): CompilerResult {
|
||||
filesToLower: Set<String>? = null,
|
||||
granularity: JsGenerationGranularity = JsGenerationGranularity.WHOLE_PROGRAM,
|
||||
): LoweredIr {
|
||||
|
||||
if (lowerPerModule) {
|
||||
return icCompile(
|
||||
depsDescriptors,
|
||||
mainArguments,
|
||||
exportedDeclarations,
|
||||
generateFullJs,
|
||||
generateDceJs,
|
||||
dceRuntimeDiagnostic,
|
||||
es6mode,
|
||||
multiModule,
|
||||
relativeRequirePath,
|
||||
propertyLazyInitialization,
|
||||
baseClassIntoMetadata,
|
||||
safeExternalBoolean,
|
||||
@@ -77,7 +77,7 @@ fun compile(
|
||||
)
|
||||
}
|
||||
|
||||
val (moduleFragment: IrModuleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer, moduleToName) =
|
||||
val (moduleFragment: IrModuleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer, _) =
|
||||
loadIr(depsDescriptors, irFactory, verifySignatures, filesToLower, loadFunctionInterfacesIntoStdlib = true)
|
||||
|
||||
val mainModule = depsDescriptors.mainModule
|
||||
@@ -102,7 +102,8 @@ fun compile(
|
||||
propertyLazyInitialization = propertyLazyInitialization,
|
||||
baseClassIntoMetadata = baseClassIntoMetadata,
|
||||
safeExternalBoolean = safeExternalBoolean,
|
||||
safeExternalBooleanDiagnostic = safeExternalBooleanDiagnostic
|
||||
safeExternalBooleanDiagnostic = safeExternalBooleanDiagnostic,
|
||||
granularity = granularity
|
||||
)
|
||||
|
||||
// Load declarations referenced during `context` initialization
|
||||
@@ -130,17 +131,6 @@ fun compile(
|
||||
eliminateDeadDeclarations(allModules, context)
|
||||
|
||||
irFactory.stageController = StageController(controller.currentStage)
|
||||
|
||||
val transformer = IrModuleToJsTransformer(
|
||||
context,
|
||||
mainArguments,
|
||||
fullJs = true,
|
||||
dceJs = false,
|
||||
multiModule = multiModule,
|
||||
relativeRequirePath = relativeRequirePath,
|
||||
moduleToName = moduleToName,
|
||||
)
|
||||
return transformer.generateModule(allModules)
|
||||
} else {
|
||||
// TODO is this reachable when lowerPerModule == true?
|
||||
if (lowerPerModule) {
|
||||
@@ -154,18 +144,9 @@ fun compile(
|
||||
} else {
|
||||
jsPhases.invokeToplevel(phaseConfig, context, allModules)
|
||||
}
|
||||
|
||||
val transformer = IrModuleToJsTransformer(
|
||||
context,
|
||||
mainArguments,
|
||||
fullJs = generateFullJs,
|
||||
dceJs = generateDceJs,
|
||||
multiModule = multiModule,
|
||||
relativeRequirePath = relativeRequirePath,
|
||||
moduleToName = moduleToName,
|
||||
)
|
||||
return transformer.generateModule(allModules)
|
||||
}
|
||||
|
||||
return LoweredIr(context, moduleFragment, allModules)
|
||||
}
|
||||
|
||||
fun lowerPreservingIcData(module: IrModuleFragment, context: JsIrBackendContext, controller: WholeWorldStageController) {
|
||||
|
||||
+9
-5
@@ -26,24 +26,28 @@ import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
|
||||
import org.jetbrains.kotlin.ir.types.*
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
||||
import org.jetbrains.kotlin.serialization.js.ModuleKind
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
|
||||
class ExportModelGenerator(val context: JsIrBackendContext) {
|
||||
class ExportModelGenerator(
|
||||
val context: JsIrBackendContext,
|
||||
val generateNamespacesForPackages: Boolean
|
||||
) {
|
||||
|
||||
fun generateExport(file: IrPackageFragment): List<ExportedDeclaration> {
|
||||
val namespaceFqName = file.fqName
|
||||
val exports = file.declarations.flatMap { declaration -> listOfNotNull(exportDeclaration(declaration)) }
|
||||
return when {
|
||||
exports.isEmpty() -> emptyList()
|
||||
namespaceFqName.isRoot -> exports
|
||||
!generateNamespacesForPackages || namespaceFqName.isRoot -> exports
|
||||
else -> listOf(ExportedNamespace(namespaceFqName.toString(), exports))
|
||||
}
|
||||
}
|
||||
|
||||
fun generateExport(modules: Iterable<IrModuleFragment>): ExportedModule =
|
||||
fun generateExport(modules: Iterable<IrModuleFragment>, moduleKind: ModuleKind = ModuleKind.PLAIN): ExportedModule =
|
||||
ExportedModule(
|
||||
context.configuration[CommonConfigurationKeys.MODULE_NAME]!!,
|
||||
context.configuration[JSConfigurationKeys.MODULE_KIND]!!,
|
||||
moduleKind,
|
||||
(context.externalPackageFragment.values + modules.flatMap { it.files }).flatMap {
|
||||
generateExport(it)
|
||||
}
|
||||
@@ -296,7 +300,7 @@ class ExportModelGenerator(val context: JsIrBackendContext) {
|
||||
|
||||
classifier is IrClassSymbol -> {
|
||||
val klass = classifier.owner
|
||||
val name = klass.fqNameWhenAvailable!!.asString()
|
||||
val name = if (generateNamespacesForPackages) klass.fqNameWhenAvailable!!.asString() else klass.name.asString()
|
||||
|
||||
when (klass.kind) {
|
||||
ClassKind.ANNOTATION_CLASS,
|
||||
|
||||
+35
-27
@@ -11,25 +11,24 @@ import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.jsAssignment
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.IrNamer
|
||||
import org.jetbrains.kotlin.js.backend.ast.*
|
||||
|
||||
|
||||
class ExportModelToJsStatements(
|
||||
private val internalModuleName: JsName,
|
||||
private val namer: IrNamer,
|
||||
private val declareNewNamespace: (String) -> String
|
||||
) {
|
||||
private val namespaceToRefMap = mutableMapOf<String, JsNameRef>()
|
||||
|
||||
fun generateModuleExport(module: ExportedModule): List<JsStatement> {
|
||||
fun generateModuleExport(module: ExportedModule, internalModuleName: JsName): List<JsStatement> {
|
||||
return module.declarations.flatMap { generateDeclarationExport(it, JsNameRef(internalModuleName)) }
|
||||
}
|
||||
|
||||
private fun generateDeclarationExport(declaration: ExportedDeclaration, namespace: JsNameRef): List<JsStatement> {
|
||||
fun generateDeclarationExport(declaration: ExportedDeclaration, namespace: JsNameRef?): List<JsStatement> {
|
||||
return when (declaration) {
|
||||
is ExportedNamespace -> {
|
||||
require(namespace != null) { "Only namespaced namespaces are allowed" }
|
||||
val statements = mutableListOf<JsStatement>()
|
||||
val elements = declaration.name.split(".")
|
||||
var currentNamespace = ""
|
||||
var currentRef = namespace
|
||||
var currentRef: JsNameRef = namespace
|
||||
for (element in elements) {
|
||||
val newNamespace = "$currentNamespace$$element"
|
||||
val newNameSpaceRef = namespaceToRefMap.getOrPut(newNamespace) {
|
||||
@@ -37,14 +36,15 @@ class ExportModelToJsStatements(
|
||||
val varRef = JsNameRef(varName)
|
||||
val namespaceRef = JsNameRef(element, currentRef)
|
||||
statements += JsVars(
|
||||
JsVars.JsVar(JsName(varName),
|
||||
JsAstUtils.or(
|
||||
namespaceRef,
|
||||
jsAssignment(
|
||||
namespaceRef,
|
||||
JsObjectLiteral()
|
||||
)
|
||||
)
|
||||
JsVars.JsVar(
|
||||
JsName(varName),
|
||||
JsAstUtils.or(
|
||||
namespaceRef,
|
||||
jsAssignment(
|
||||
namespaceRef,
|
||||
JsObjectLiteral()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
varRef
|
||||
@@ -56,17 +56,23 @@ class ExportModelToJsStatements(
|
||||
}
|
||||
|
||||
is ExportedFunction -> {
|
||||
listOf(
|
||||
jsAssignment(
|
||||
JsNameRef(declaration.name, namespace),
|
||||
JsNameRef(namer.getNameForStaticDeclaration(declaration.ir))
|
||||
).makeStmt()
|
||||
)
|
||||
val name = namer.getNameForStaticDeclaration(declaration.ir)
|
||||
if (namespace == null) {
|
||||
listOf(JsExport(name, alias = JsName(declaration.name)))
|
||||
} else {
|
||||
listOf(
|
||||
jsAssignment(
|
||||
JsNameRef(declaration.name, namespace),
|
||||
JsNameRef(name)
|
||||
).makeStmt()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is ExportedConstructor -> emptyList()
|
||||
|
||||
is ExportedProperty -> {
|
||||
require(namespace != null) { "Only namespaced properties are allowed" }
|
||||
val getter = declaration.irGetter?.let { JsNameRef(namer.getNameForStaticDeclaration(it)) }
|
||||
val setter = declaration.irSetter?.let { JsNameRef(namer.getNameForStaticDeclaration(it)) }
|
||||
listOf(defineProperty(namespace, declaration.name, getter, setter).makeStmt())
|
||||
@@ -77,14 +83,16 @@ class ExportModelToJsStatements(
|
||||
is ExportedClass -> {
|
||||
if (declaration.isInterface) return emptyList()
|
||||
val newNameSpace = JsNameRef(declaration.name, namespace)
|
||||
val klassExport = jsAssignment(
|
||||
newNameSpace,
|
||||
JsNameRef(
|
||||
namer.getNameForStaticDeclaration(
|
||||
declaration.ir
|
||||
)
|
||||
)
|
||||
).makeStmt()
|
||||
val name = namer.getNameForStaticDeclaration(declaration.ir)
|
||||
val klassExport =
|
||||
if (namespace == null) {
|
||||
JsExport(name, alias = JsName(declaration.name))
|
||||
} else {
|
||||
jsAssignment(
|
||||
newNameSpace,
|
||||
JsNameRef(name)
|
||||
).makeStmt()
|
||||
}
|
||||
|
||||
// These are only used when exporting secondary constructors annotated with @JsName
|
||||
val staticFunctions = declaration.members.filter { it is ExportedFunction && it.isStatic }
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ fun ExportedModule.toTypeScript(): String {
|
||||
|
||||
return when (moduleKind) {
|
||||
ModuleKind.PLAIN -> "declare namespace $namespaceName {\n$declarationsDts\n}\n"
|
||||
ModuleKind.AMD, ModuleKind.COMMON_JS -> declarationsDts
|
||||
ModuleKind.AMD, ModuleKind.COMMON_JS, ModuleKind.ES -> declarationsDts
|
||||
ModuleKind.UMD -> "$declarationsDts\nexport as namespace $namespaceName;"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,25 +123,20 @@ private fun dumpIr(module: IrModuleFragment, fileName: String) {
|
||||
|
||||
fun icCompile(
|
||||
depsDescriptor: ModulesStructure,
|
||||
mainArguments: List<String>?,
|
||||
exportedDeclarations: Set<FqName> = emptySet(),
|
||||
generateFullJs: Boolean = true,
|
||||
generateDceJs: Boolean = false,
|
||||
dceRuntimeDiagnostic: RuntimeDiagnostic? = null,
|
||||
es6mode: Boolean = false,
|
||||
multiModule: Boolean = false,
|
||||
relativeRequirePath: Boolean = false,
|
||||
propertyLazyInitialization: Boolean,
|
||||
baseClassIntoMetadata: Boolean = false,
|
||||
safeExternalBoolean: Boolean = false,
|
||||
safeExternalBooleanDiagnostic: RuntimeDiagnostic? = null,
|
||||
): CompilerResult {
|
||||
): LoweredIr {
|
||||
|
||||
val irFactory = PersistentIrFactory()
|
||||
val controller = WholeWorldStageController()
|
||||
irFactory.stageController = controller
|
||||
|
||||
val (context, _, allModules, moduleToName, loweredIrLoaded) = prepareIr(
|
||||
val (context, _, allModules, _, loweredIrLoaded) = prepareIr(
|
||||
depsDescriptor,
|
||||
exportedDeclarations,
|
||||
dceRuntimeDiagnostic,
|
||||
@@ -155,8 +150,6 @@ fun icCompile(
|
||||
|
||||
val modulesToLower = allModules.filter { it !in loweredIrLoaded }
|
||||
|
||||
|
||||
|
||||
if (!modulesToLower.isEmpty()) {
|
||||
// This won't work incrementally
|
||||
modulesToLower.forEach { module ->
|
||||
@@ -172,20 +165,7 @@ fun icCompile(
|
||||
|
||||
// dumpIr(allModules.first(), "simple-dump${if (useStdlibCache) "-actual" else ""}")
|
||||
|
||||
val transformer = IrModuleToJsTransformer(
|
||||
context,
|
||||
mainArguments,
|
||||
fullJs = generateFullJs,
|
||||
dceJs = generateDceJs,
|
||||
multiModule = multiModule,
|
||||
relativeRequirePath = relativeRequirePath,
|
||||
moduleToName = moduleToName,
|
||||
removeUnusedAssociatedObjects = false,
|
||||
)
|
||||
|
||||
irFactory.stageController = object : StageController(999) {}
|
||||
|
||||
return transformer.generateModule(allModules)
|
||||
return LoweredIr(context, allModules.last(), allModules)
|
||||
}
|
||||
|
||||
fun lowerPreservingIcData(module: IrModuleFragment, context: JsIrBackendContext, controller: WholeWorldStageController) {
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.ir.backend.js.lower
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.lower.optimizations.PropertyAccessorInlineLowering
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
|
||||
import org.jetbrains.kotlin.ir.declarations.IrProperty
|
||||
import org.jetbrains.kotlin.ir.util.fileOrNull
|
||||
|
||||
class JsPropertyAccessorInlineLowering(
|
||||
val context: JsIrBackendContext
|
||||
) : PropertyAccessorInlineLowering(context) {
|
||||
override fun IrProperty.isSafeToInline(accessContainer: IrDeclaration): Boolean {
|
||||
if (!isSafeToInlineInClosedWorld())
|
||||
return false
|
||||
|
||||
if (isConst)
|
||||
return true
|
||||
|
||||
return when (context.granularity) {
|
||||
JsGenerationGranularity.WHOLE_PROGRAM ->
|
||||
true
|
||||
JsGenerationGranularity.PER_MODULE -> {
|
||||
val accessModule = accessContainer.fileOrNull?.module ?: return false
|
||||
val module = fileOrNull?.module ?: return false
|
||||
accessModule == module
|
||||
}
|
||||
JsGenerationGranularity.PER_FILE ->
|
||||
// Not inlining because
|
||||
// 1. we need a way to distinguish per-file generation units
|
||||
// 2. per-file mode intended for debug builds only at the moment
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.ir.backend.js.lower
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclarationContainer
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.IrFileSymbolImpl
|
||||
import org.jetbrains.kotlin.ir.util.transformDeclarationsFlat
|
||||
import org.jetbrains.kotlin.ir.util.transformFlat
|
||||
|
||||
fun moveOpenClassesToSeparateFiles(moduleFragment: IrModuleFragment) {
|
||||
fun createFile(file: IrFile, klass: IrClass): IrFile =
|
||||
IrFileImpl(fileEntry = file.fileEntry, fqName = file.fqName, symbol = IrFileSymbolImpl(), module = file.module).also {
|
||||
it.annotations += file.annotations
|
||||
it.declarations += klass
|
||||
klass.parent = it
|
||||
}
|
||||
|
||||
moduleFragment.files.transformFlat { file ->
|
||||
// We don't have to split declarations with a single class
|
||||
if (file.declarations.size <= 1)
|
||||
return@transformFlat null
|
||||
|
||||
val openClasses = mutableListOf<IrClass>()
|
||||
fun removeAndCollectOpenClasses(container: IrDeclarationContainer) {
|
||||
container.transformDeclarationsFlat { declaration ->
|
||||
if (declaration is IrDeclarationContainer) {
|
||||
removeAndCollectOpenClasses(declaration)
|
||||
}
|
||||
if (
|
||||
declaration is IrClass &&
|
||||
(declaration.modality == Modality.OPEN || declaration.modality == Modality.ABSTRACT) &&
|
||||
declaration.kind == ClassKind.CLASS &&
|
||||
declaration.visibility != DescriptorVisibilities.PRIVATE &&
|
||||
declaration.visibility != DescriptorVisibilities.LOCAL
|
||||
) {
|
||||
openClasses += declaration
|
||||
emptyList()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
removeAndCollectOpenClasses(file)
|
||||
|
||||
return@transformFlat if (openClasses.isEmpty())
|
||||
null
|
||||
else
|
||||
listOf(file) + openClasses.map { createFile(file, it) }
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
||||
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.declarations.IrPackageFragment
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsBlock
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsGlobalBlock
|
||||
|
||||
|
||||
+5
-1
@@ -6,15 +6,19 @@
|
||||
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
||||
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrConstructor
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||
import org.jetbrains.kotlin.ir.util.isInterface
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsFunction
|
||||
|
||||
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
|
||||
class IrFunctionToJsTransformer : BaseIrElementToJsNodeTransformer<JsFunction, JsGenerationContext> {
|
||||
override fun visitSimpleFunction(declaration: IrSimpleFunction, context: JsGenerationContext): JsFunction {
|
||||
val funcName = if (declaration.dispatchReceiverParameter == null) {
|
||||
val parentClass = declaration.parent as? IrClass
|
||||
val isInterfaceDefaultImpl = parentClass?.isInterface ?: false
|
||||
val funcName = if (declaration.dispatchReceiverParameter == null || isInterfaceDefaultImpl) {
|
||||
if (declaration.parent is IrFunction) {
|
||||
context.getNameForValueDeclaration(declaration)
|
||||
} else {
|
||||
|
||||
+30
-29
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.js.backend.JsToStringGenerationVisitor
|
||||
import org.jetbrains.kotlin.js.backend.NoOpSourceLocationConsumer
|
||||
import org.jetbrains.kotlin.js.backend.ast.*
|
||||
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
||||
import org.jetbrains.kotlin.serialization.js.ModuleKind
|
||||
import org.jetbrains.kotlin.js.config.SourceMapSourceEmbedding
|
||||
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver
|
||||
import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder
|
||||
@@ -52,17 +53,14 @@ class IrModuleToJsTransformer(
|
||||
) + packageLevelJsModules
|
||||
}
|
||||
|
||||
val exportedModule = ExportModelGenerator(backendContext).generateExport(modules)
|
||||
val moduleKind: ModuleKind = backendContext.configuration[JSConfigurationKeys.MODULE_KIND]!!
|
||||
val exportedModule = ExportModelGenerator(backendContext, generateNamespacesForPackages = true).generateExport(modules, moduleKind = moduleKind)
|
||||
val dts = exportedModule.toTypeScript()
|
||||
|
||||
modules.forEach { module ->
|
||||
module.files.forEach { StaticMembersLowering(backendContext).lower(it) }
|
||||
}
|
||||
|
||||
if (multiModule) {
|
||||
breakCrossModuleFieldAccess(backendContext, modules)
|
||||
}
|
||||
|
||||
modules.forEach { module ->
|
||||
namer.merge(module.files, additionalPackages)
|
||||
}
|
||||
@@ -103,7 +101,7 @@ class IrModuleToJsTransformer(
|
||||
val dependencies = others.mapIndexed { index, module ->
|
||||
val moduleName = module.externalModuleName()
|
||||
|
||||
val exportedDeclarations = ExportModelGenerator(backendContext).let { module.files.flatMap { file -> it.generateExport(file) } }
|
||||
val exportedDeclarations = ExportModelGenerator(backendContext, generateNamespacesForPackages = true).let { module.files.flatMap { file -> it.generateExport(file) } }
|
||||
|
||||
moduleName to generateWrappedModuleBody2(
|
||||
listOf(module),
|
||||
@@ -149,7 +147,7 @@ class IrModuleToJsTransformer(
|
||||
currentFile = null,
|
||||
currentFunction = null,
|
||||
staticContext = staticContext,
|
||||
localNames = LocalNameGenerator(NameScope.EmptyScope)
|
||||
localNames = LocalNameGenerator(NameTable())
|
||||
)
|
||||
|
||||
val (importStatements, importedJsModules) =
|
||||
@@ -162,7 +160,8 @@ class IrModuleToJsTransformer(
|
||||
|
||||
val internalModuleName = JsName("_")
|
||||
val globalNames = NameTable<String>(namer.globalNames)
|
||||
val exportStatements = ExportModelToJsStatements(internalModuleName, nameGenerator, { globalNames.declareFreshName(it, it)}).generateModuleExport(exportedModule)
|
||||
val exportStatements = ExportModelToJsStatements(nameGenerator) { globalNames.declareFreshName(it, it) }
|
||||
.generateModuleExport(exportedModule, internalModuleName)
|
||||
|
||||
val (crossModuleImports, importedKotlinModules) = generateCrossModuleImports(nameGenerator, modules, dependencies, { JsName(sanitizeName(it)) })
|
||||
val crossModuleExports = generateCrossModuleExports(modules, refInfo, internalModuleName)
|
||||
@@ -431,27 +430,6 @@ class IrModuleToJsTransformer(
|
||||
return Pair(importStatements, importedJsModules)
|
||||
}
|
||||
|
||||
private fun processClassModels(
|
||||
classModelMap: Map<IrClassSymbol, JsIrClassModel>,
|
||||
preDeclarationBlock: JsBlock,
|
||||
postDeclarationBlock: JsBlock
|
||||
) {
|
||||
val declarationHandler = object : DFS.AbstractNodeHandler<IrClassSymbol, Unit>() {
|
||||
override fun result() {}
|
||||
override fun afterChildren(current: IrClassSymbol) {
|
||||
classModelMap[current]?.let {
|
||||
preDeclarationBlock.statements += it.preDeclarationBlock.statements
|
||||
postDeclarationBlock.statements += it.postDeclarationBlock.statements
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DFS.dfs(
|
||||
classModelMap.keys,
|
||||
{ klass -> classModelMap[klass]?.superClasses ?: emptyList() },
|
||||
declarationHandler
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<JsStatement>.startRegion(description: String = "") {
|
||||
if (generateRegionComments) {
|
||||
@@ -479,3 +457,26 @@ class IrModuleToJsTransformer(
|
||||
endRegion()
|
||||
}
|
||||
}
|
||||
|
||||
fun processClassModels(
|
||||
classModelMap: Map<IrClassSymbol, JsIrClassModel>,
|
||||
preDeclarationBlock: JsBlock,
|
||||
postDeclarationBlock: JsBlock
|
||||
) {
|
||||
val declarationHandler = object : DFS.AbstractNodeHandler<IrClassSymbol, Unit>() {
|
||||
override fun result() {}
|
||||
override fun afterChildren(current: IrClassSymbol) {
|
||||
classModelMap[current]?.let {
|
||||
preDeclarationBlock.statements += it.preDeclarationBlock.statements
|
||||
postDeclarationBlock.statements += it.postDeclarationBlock.statements
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DFS.dfs(
|
||||
classModelMap.keys,
|
||||
{ klass -> classModelMap[klass]?.superClasses ?: emptyList() },
|
||||
declarationHandler
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+19
-9
@@ -85,7 +85,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
|
||||
}
|
||||
}
|
||||
is IrClass -> {
|
||||
classBlock.statements += JsClassGenerator(declaration, context).generate()
|
||||
// classBlock.statements += JsClassGenerator(declaration, context).generate()
|
||||
}
|
||||
is IrField -> {
|
||||
}
|
||||
@@ -208,9 +208,14 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
|
||||
val memberRef = JsNameRef(memberName, classPrototypeRef)
|
||||
|
||||
if (declaration.isReal && declaration.body != null) {
|
||||
val translatedFunction = declaration.accept(IrFunctionToJsTransformer(), context)
|
||||
val translatedFunction: JsFunction = declaration.accept(IrFunctionToJsTransformer(), context)
|
||||
assert(!declaration.isStaticMethodOfClass)
|
||||
|
||||
if (irClass.isInterface) {
|
||||
classModel.preDeclarationBlock.statements += translatedFunction.makeStmt()
|
||||
return Pair(memberRef, null)
|
||||
}
|
||||
|
||||
return Pair(memberRef, translatedFunction)
|
||||
}
|
||||
|
||||
@@ -224,14 +229,19 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
|
||||
?.let {
|
||||
val implClassDeclaration = it.parent as IrClass
|
||||
|
||||
if (implClassDeclaration.shouldCopyFrom()) {
|
||||
val implMethodName = context.getNameForMemberFunction(it)
|
||||
val implClassName = context.getNameForClass(implClassDeclaration)
|
||||
if (implClassDeclaration.shouldCopyFrom() && it.body != null) {
|
||||
val reference = context.getNameForStaticDeclaration(it).makeRef()
|
||||
classModel.postDeclarationBlock.statements += jsAssignment(memberRef, reference).makeStmt()
|
||||
}
|
||||
}
|
||||
declaration.collectRealOverrides()
|
||||
.find { it.modality != Modality.ABSTRACT }
|
||||
?.let {
|
||||
val implClassDeclaration = it.parent as IrClass
|
||||
|
||||
val implClassPrototype = prototypeOf(implClassName.makeRef())
|
||||
val implMemberRef = JsNameRef(implMethodName, implClassPrototype)
|
||||
|
||||
classModel.postDeclarationBlock.statements += jsAssignment(memberRef, implMemberRef).makeStmt()
|
||||
if (implClassDeclaration.shouldCopyFrom() && it.body != null) {
|
||||
val reference = context.getNameForStaticDeclaration(it).makeRef()
|
||||
classModel.postDeclarationBlock.statements += jsAssignment(memberRef, reference).makeStmt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -25,6 +25,7 @@ object ModuleWrapperTranslation {
|
||||
ModuleKind.COMMON_JS -> wrapCommonJs(function, importedModules, program)
|
||||
ModuleKind.UMD -> wrapUmd(moduleId, function, importedModules, program)
|
||||
ModuleKind.PLAIN -> wrapPlain(moduleId, function, importedModules, program)
|
||||
ModuleKind.ES -> error("ES modules are not supported in legacy wrapper")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-88
@@ -116,94 +116,6 @@ fun buildCrossModuleReferenceInfo(modules: Iterable<IrModuleFragment>): CrossMod
|
||||
return CrossModuleReferenceInfoImpl(map)
|
||||
}
|
||||
|
||||
fun breakCrossModuleFieldAccess(
|
||||
context: JsIrBackendContext,
|
||||
modules: Iterable<IrModuleFragment>
|
||||
) {
|
||||
val fieldToGetter = mutableMapOf<IrField, IrSimpleFunction>()
|
||||
|
||||
fun IrField.getter(): IrSimpleFunction {
|
||||
return fieldToGetter.getOrPut(this) {
|
||||
val fieldName = name
|
||||
val getter = context.irFactory.buildFun {
|
||||
name = Name.identifier("get-$fieldName")
|
||||
returnType = type
|
||||
}
|
||||
getter.body = factory.createBlockBody(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, listOf(
|
||||
IrReturnImpl(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, getter.symbol,
|
||||
IrGetFieldImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, symbol, type)
|
||||
)
|
||||
)
|
||||
)
|
||||
getter.parent = parent
|
||||
(parent as IrDeclarationContainer).declarations += getter
|
||||
|
||||
getter
|
||||
}
|
||||
}
|
||||
|
||||
val fieldToSetter = mutableMapOf<IrField, IrSimpleFunction>()
|
||||
|
||||
fun IrField.setter(): IrSimpleFunction {
|
||||
return fieldToSetter.getOrPut(this) {
|
||||
val fieldName = name
|
||||
val setter = context.irFactory.buildFun {
|
||||
name = Name.identifier("set-$fieldName")
|
||||
returnType = context.irBuiltIns.unitType
|
||||
}
|
||||
|
||||
val param = setter.addValueParameter("value", type)
|
||||
|
||||
setter.body = factory.createBlockBody(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, listOf(
|
||||
IrSetFieldImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, symbol, type).apply {
|
||||
value = IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, param.symbol)
|
||||
}
|
||||
)
|
||||
)
|
||||
setter.parent = parent
|
||||
(parent as IrDeclarationContainer).declarations += setter
|
||||
|
||||
setter
|
||||
}
|
||||
}
|
||||
|
||||
modules.reversed().forEach { module ->
|
||||
|
||||
val moduleFields = module.files.flatMap { it.declarations.filterIsInstance<IrField>() }.toSet()
|
||||
|
||||
fun IrField.transformAccess(fn: IrField.() -> IrCall): IrCall? {
|
||||
if (parent !is IrPackageFragment || isEffectivelyExternal() || this in moduleFields) return null
|
||||
return fn()
|
||||
}
|
||||
|
||||
module.transformChildrenVoid(object : IrElementTransformerVoid() {
|
||||
|
||||
override fun visitGetField(expression: IrGetField): IrExpression {
|
||||
expression.transformChildrenVoid(this)
|
||||
|
||||
return expression.symbol.owner.transformAccess {
|
||||
val getter = getter()
|
||||
IrCallImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, getter.returnType, getter.symbol, getter.typeParameters.size, getter.valueParameters.size)
|
||||
} ?: expression
|
||||
}
|
||||
|
||||
override fun visitSetField(expression: IrSetField): IrExpression {
|
||||
expression.transformChildrenVoid(this)
|
||||
|
||||
return expression.symbol.owner.transformAccess {
|
||||
val setter = setter()
|
||||
IrCallImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, setter.returnType, setter.symbol, setter.typeParameters.size, setter.valueParameters.size).apply {
|
||||
putValueArgument(0, expression.value)
|
||||
}
|
||||
} ?: expression
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
val IrModuleFragment.safeName: String
|
||||
get() {
|
||||
var result = name.asString()
|
||||
|
||||
+20
-19
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.*
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
@@ -127,17 +128,22 @@ fun translateCall(
|
||||
}
|
||||
|
||||
expression.superQualifierSymbol?.let { superQualifier ->
|
||||
val (target, klass) = if (superQualifier.owner.isInterface) {
|
||||
val (target: IrSimpleFunction, klass: IrClass) = if (superQualifier.owner.isInterface) {
|
||||
val impl = function.resolveFakeOverride()!!
|
||||
Pair(impl, impl.parentAsClass)
|
||||
} else {
|
||||
Pair(function, superQualifier.owner)
|
||||
}
|
||||
|
||||
val qualifierName = context.getNameForClass(klass).makeRef()
|
||||
val targetName = context.getNameForMemberFunction(target)
|
||||
val qPrototype = JsNameRef(targetName, prototypeOf(qualifierName))
|
||||
val callRef = JsNameRef(Namer.CALL_FUNCTION, qPrototype)
|
||||
val callRef = if (klass.isInterface && target.body != null) {
|
||||
JsNameRef(Namer.CALL_FUNCTION, JsNameRef(context.getNameForStaticDeclaration(target)))
|
||||
} else {
|
||||
val qualifierName = context.getNameForClass(klass).makeRef()
|
||||
val targetName = context.getNameForMemberFunction(target)
|
||||
val qPrototype = JsNameRef(targetName, prototypeOf(qualifierName))
|
||||
JsNameRef(Namer.CALL_FUNCTION, qPrototype)
|
||||
}
|
||||
|
||||
return JsInvocation(callRef, jsDispatchReceiver?.let { receiver -> listOf(receiver) + arguments } ?: arguments)
|
||||
}
|
||||
|
||||
@@ -193,20 +199,15 @@ fun translateCall(
|
||||
"VarargIIFE"
|
||||
)
|
||||
|
||||
val iifeFunHasContext = (jsDispatchReceiver as? JsNameRef)?.qualifier is JsThisRef
|
||||
if (iifeFunHasContext) {
|
||||
JsInvocation(
|
||||
// Create scope for temporary variable holding dispatch receiver
|
||||
// It is used both during method reference and passing `this` value to `apply` function.
|
||||
JsNameRef(
|
||||
"call",
|
||||
iifeFun
|
||||
),
|
||||
JsThisRef()
|
||||
)
|
||||
} else {
|
||||
JsInvocation(iifeFun)
|
||||
}
|
||||
JsInvocation(
|
||||
// Create scope for temporary variable holding dispatch receiver
|
||||
// It is used both during method reference and passing `this` value to `apply` function.
|
||||
JsNameRef(
|
||||
"call",
|
||||
iifeFun
|
||||
),
|
||||
JsThisRef()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (argumentsAsSingleArray is JsArrayLiteral) {
|
||||
|
||||
+2
-1
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.ir.backend.js.utils
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIntrinsicTransformers
|
||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrClassModel
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
|
||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsGlobalBlock
|
||||
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
||||
@@ -16,7 +17,7 @@ import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
||||
class JsStaticContext(
|
||||
val backendContext: JsIrBackendContext,
|
||||
private val irNamer: IrNamer,
|
||||
val globalNameScope: NameScope,
|
||||
val globalNameScope: NameTable<IrDeclaration>,
|
||||
) : IrNamer by irNamer {
|
||||
val intrinsics = JsIntrinsicTransformers(backendContext)
|
||||
val classModels = mutableMapOf<IrClassSymbol, JsIrClassModel>()
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.ir.types.isUnit
|
||||
import org.jetbrains.kotlin.ir.util.file
|
||||
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
|
||||
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
|
||||
import org.jetbrains.kotlin.ir.util.isInterface
|
||||
import org.jetbrains.kotlin.ir.util.render
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
@@ -32,7 +33,12 @@ import kotlin.math.abs
|
||||
private fun <T> mapToKey(declaration: T): String {
|
||||
return with(JsManglerIr) {
|
||||
if (declaration is IrDeclaration) {
|
||||
declaration.hashedMangle(compatibleMode = false).toString()
|
||||
try {
|
||||
declaration.hashedMangle(compatibleMode = false).toString()
|
||||
} catch (e: Throwable) {
|
||||
// FIXME: We can't mangle some local declarations. But
|
||||
"wrong_key"
|
||||
}
|
||||
} else if (declaration is String) {
|
||||
declaration.hashMangle.toString()
|
||||
} else {
|
||||
@@ -118,6 +124,7 @@ fun jsFunctionSignature(declaration: IrFunction, context: JsIrBackendContext?):
|
||||
}
|
||||
|
||||
val nameBuilder = StringBuilder()
|
||||
nameBuilder.append(declarationName)
|
||||
|
||||
// TODO should we skip type parameters and use upper bound of type parameter when print type of value parameters?
|
||||
declaration.typeParameters.ifNotEmpty {
|
||||
@@ -202,6 +209,11 @@ class NameTables(
|
||||
when (memberDecl) {
|
||||
is IrField ->
|
||||
generateNameForMemberField(memberDecl)
|
||||
is IrSimpleFunction -> {
|
||||
if (declaration.isInterface && memberDecl.body != null) {
|
||||
globalNames.declareFreshName(memberDecl, memberDecl.name.asString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,8 +311,7 @@ class NameTables(
|
||||
|
||||
}
|
||||
|
||||
class LocalNameGenerator(parentScope: NameScope) : IrElementVisitorVoid {
|
||||
val variableNames = NameTable<IrDeclarationWithName>(parentScope)
|
||||
class LocalNameGenerator(val variableNames: NameTable<IrDeclaration>) : IrElementVisitorVoid {
|
||||
val localLoopNames = NameTable<IrLoop>()
|
||||
val localReturnableBlockNames = NameTable<IrReturnableBlock>()
|
||||
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.ir.backend.js.utils
|
||||
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.codegen.IrToJs
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.IrDeclarationReference
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsImport
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsName
|
||||
|
||||
class StaticDeclarationNumerator {
|
||||
var currentNumber = 0
|
||||
val numeration = mutableMapOf<IrDeclaration, Int>()
|
||||
|
||||
fun add(moduleFragment: IrModuleFragment) {
|
||||
moduleFragment.files.forEach { add(it) }
|
||||
}
|
||||
|
||||
fun add(declaration: IrDeclaration) {
|
||||
// TODO: We should not visit declarations multiple times.
|
||||
// Investigate enum tests in dce-driven mode.
|
||||
if (declaration !in numeration) {
|
||||
numeration[declaration] = currentNumber
|
||||
currentNumber++
|
||||
}
|
||||
}
|
||||
|
||||
fun add(packageFragment: IrPackageFragment) {
|
||||
packageFragment.acceptChildrenVoid(object : IrElementVisitorVoid {
|
||||
override fun visitElement(element: IrElement) {
|
||||
element.acceptChildrenVoid(this)
|
||||
}
|
||||
|
||||
override fun visitDeclaration(declaration: IrDeclarationBase) {
|
||||
if (declaration !is IrVariable) {
|
||||
add(declaration)
|
||||
}
|
||||
super.visitDeclaration(declaration)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class NewStableStaticNamesCollectorVisitor(val needToCollectReferences: Boolean) : IrElementVisitorVoid {
|
||||
val collectedStableNames = mutableSetOf<String>()
|
||||
|
||||
init {
|
||||
collectedStableNames.addAll(RESERVED_IDENTIFIERS)
|
||||
collectedStableNames.add(Namer.IMPLICIT_RECEIVER_NAME)
|
||||
}
|
||||
|
||||
private fun IrDeclaration.collectStableName() {
|
||||
collectedStableNames += stableNameForExternalDeclaration(this) ?: return
|
||||
}
|
||||
|
||||
override fun visitElement(element: IrElement) {
|
||||
element.acceptChildrenVoid(this)
|
||||
}
|
||||
|
||||
override fun visitDeclaration(declaration: IrDeclarationBase) {
|
||||
super.visitDeclaration(declaration)
|
||||
declaration.collectStableName()
|
||||
}
|
||||
|
||||
override fun visitDeclarationReference(expression: IrDeclarationReference) {
|
||||
super.visitDeclarationReference(expression)
|
||||
if (needToCollectReferences) {
|
||||
val declaration = expression.symbol.owner as? IrDeclaration
|
||||
declaration?.collectStableName()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NewNamerImpl(
|
||||
val context: JsIrBackendContext,
|
||||
val unit: IrToJs.CodegenUnit,
|
||||
val exportId: (IrDeclarationWithName) -> String,
|
||||
val stableNames: Set<String>,
|
||||
) : IrNamerBase() {
|
||||
val staticNames = NameTable<IrDeclaration>(
|
||||
reserved = stableNames.toMutableSet()
|
||||
)
|
||||
val internalImports = mutableMapOf<String, JsImport>()
|
||||
|
||||
override fun getNameForMemberFunction(function: IrSimpleFunction): JsName {
|
||||
require(function.dispatchReceiverParameter != null)
|
||||
val name = jsFunctionSignature(function, context)
|
||||
return name.toJsName()
|
||||
}
|
||||
|
||||
override fun getNameForMemberField(field: IrField): JsName {
|
||||
val fieldName = sanitizeName(
|
||||
try {
|
||||
exportId(field)
|
||||
} catch (e: IllegalStateException) {
|
||||
// TODO: Fix DCE with inline classes and remove this hack
|
||||
field.name.asString() + "_LIKELY_ELIMINATED_BY_DCE"
|
||||
}
|
||||
)
|
||||
// TODO: Webpack not minimize member names, it is long name, which is not minimized, so it affects final JS bundle size
|
||||
// Use shorter names
|
||||
return JsName("f_$fieldName")
|
||||
}
|
||||
|
||||
override fun getNameForStaticDeclaration(declaration: IrDeclarationWithName): JsName {
|
||||
staticNames.names[declaration]?.let { return JsName(it) }
|
||||
|
||||
fun registerImport(moduleId: String, importedName: String) {
|
||||
val fullModuleId = if (moduleId.startsWith(".")) {
|
||||
unit.pathToKotlinModulesRoot + moduleId
|
||||
} else {
|
||||
// TODO: Do we cover this path in tests?
|
||||
moduleId
|
||||
}
|
||||
|
||||
val import = internalImports.getOrPut(fullModuleId) {
|
||||
JsImport(fullModuleId)
|
||||
}
|
||||
import.elements += JsImport.Element(importedName, staticNames.names[declaration]!!)
|
||||
}
|
||||
|
||||
if (declaration.isEffectivelyExternal()) {
|
||||
val jsModule: String? = declaration.getJsModule()
|
||||
val maybeParentFile: IrFile? = declaration.parent as? IrFile
|
||||
val fileJsModule: String? = maybeParentFile?.getJsModule()
|
||||
val jsQualifier: String? = maybeParentFile?.getJsQualifier()
|
||||
|
||||
when {
|
||||
jsModule != null -> {
|
||||
// TODO: Support jsQualifier
|
||||
staticNames.declareFreshName(declaration, declaration.name.asString())
|
||||
registerImport(jsModule, "default")
|
||||
}
|
||||
|
||||
fileJsModule != null -> {
|
||||
// TODO: Support jsQualifier
|
||||
staticNames.declareFreshName(declaration, declaration.name.asString())
|
||||
registerImport(fileJsModule, declaration.getJsNameOrKotlinName().identifier)
|
||||
}
|
||||
|
||||
else -> {
|
||||
var name = declaration.getJsNameOrKotlinName().identifier
|
||||
if (jsQualifier != null)
|
||||
name = "$jsQualifier.$name"
|
||||
|
||||
staticNames.declareStableName(declaration, name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else { // Non-external declaration
|
||||
staticNames.declareFreshName(declaration, declaration.name.asString())
|
||||
val unitReference = unit.referenceCodegenUnitOfDeclaration(declaration)
|
||||
if (unitReference is IrToJs.OtherUnitReference) {
|
||||
registerImport(unitReference.importPath, exportId(declaration))
|
||||
}
|
||||
}
|
||||
|
||||
return JsName(staticNames.names[declaration]!!)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Cache?
|
||||
private fun stableNameForExternalDeclaration(declaration: IrDeclaration): String? {
|
||||
if (declaration !is IrDeclarationWithName ||
|
||||
!declaration.hasStaticDispatch() ||
|
||||
!declaration.isEffectivelyExternal() ||
|
||||
declaration.isPropertyAccessor ||
|
||||
declaration.isPropertyField
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (declaration is IrConstructor) {
|
||||
return stableNameForExternalDeclaration(declaration.parentAsClass)
|
||||
}
|
||||
|
||||
val importedFromModuleOnly =
|
||||
declaration.getJsModule() != null && !declaration.isJsNonModule()
|
||||
|
||||
val jsName = declaration.getJsName()
|
||||
|
||||
val jsQualifier = declaration.fileOrNull?.getJsQualifier()
|
||||
|
||||
return when {
|
||||
importedFromModuleOnly ->
|
||||
null
|
||||
|
||||
jsQualifier != null ->
|
||||
jsQualifier.split('1')[0]
|
||||
|
||||
jsName != null ->
|
||||
jsName
|
||||
|
||||
else ->
|
||||
declaration.name.identifier
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -20,6 +20,7 @@ where advanced options include:
|
||||
Enable runtime diagnostics when performing DCE instead of removing declarations
|
||||
-Xir-module-name=<name> Specify a compilation module name for IR backend
|
||||
-Xir-only Disables pre-IR backend
|
||||
-Xir-per-file Splits generated .js per-file
|
||||
-Xir-per-module Splits generated .js per-module
|
||||
-Xir-per-module-output-name Adds a custom output name to the splitted js files
|
||||
-Xir-produce-js Generates JS file using IR backend. Also disables pre-IR backend
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// SKIP_DCE_DRIVEN
|
||||
|
||||
enum class Bar {
|
||||
ONE {
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
// TARGET_BACKEND: JS_IR
|
||||
// PROPERTY_LAZY_INITIALIZATION
|
||||
|
||||
// FILE: A.kt
|
||||
|
||||
val a = "A"
|
||||
|
||||
// FILE: B.kt
|
||||
val b = "B".let {
|
||||
it + "B"
|
||||
}
|
||||
|
||||
val c = b
|
||||
|
||||
// FILE: C.kt
|
||||
val d = "D".let {
|
||||
it + "D"
|
||||
}
|
||||
|
||||
val e = d
|
||||
|
||||
// FILE: main.kt
|
||||
|
||||
fun box(): String {
|
||||
// Get only e to initialize all properties in file
|
||||
e
|
||||
return if (
|
||||
js("a") === "A" &&
|
||||
js("typeof b") == "undefined" &&
|
||||
js("typeof c") == "undefined" &&
|
||||
js("d") === "DD" &&
|
||||
js("e") === "DD"
|
||||
)
|
||||
"OK"
|
||||
else "a = ${js("a")}; typeof b = ${js("typeof b")}; typeof c = ${js("typeof c")}; d = ${js("d")}; e = ${js("e")}"
|
||||
}
|
||||
+4
-1
@@ -3,7 +3,10 @@
|
||||
// PROPERTY_LAZY_INITIALIZATION
|
||||
|
||||
// FILE: A.kt
|
||||
var result: String? = null
|
||||
|
||||
val a = "a".let {
|
||||
result = "OK"
|
||||
it + "a"
|
||||
}
|
||||
|
||||
@@ -13,5 +16,5 @@ fun foo() =
|
||||
// FILE: main.kt
|
||||
fun box(): String {
|
||||
val foo = foo()
|
||||
return if (js("typeof a") == "string" && js("a") == "aa") "OK" else "fail"
|
||||
return result!!
|
||||
}
|
||||
+2
-1
@@ -5,6 +5,7 @@
|
||||
|
||||
// FILE: A.kt
|
||||
val a1 = "a".let {
|
||||
throw Error()
|
||||
it + "a"
|
||||
}
|
||||
|
||||
@@ -39,5 +40,5 @@ fun box(): String {
|
||||
C.values()
|
||||
C.valueOf("OK")
|
||||
val baz = b
|
||||
return if (js("typeof a1") == "undefined") "OK" else "fail"
|
||||
return "OK"
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
// TODO: Investigate ES modules issue
|
||||
|
||||
// FILE: Outer.kt
|
||||
|
||||
package another
|
||||
|
||||
Reference in New Issue
Block a user