JS_IR: DCE

Could be enabled by toggling `-Xir-dce`

Box test output in js/js.translator/testData/out-min
This commit is contained in:
Anton Bannykh
2019-11-19 15:36:27 +03:00
parent faf4c05fc8
commit 81699299f5
23 changed files with 553 additions and 173 deletions
@@ -122,6 +122,9 @@ class K2JSCompilerArguments : CommonCompilerArguments() {
@Argument(value = "-Xir-produce-js", description = "Generates JS file using IR backend. Also disables pre-IR backend")
var irProduceJs: Boolean by FreezableVar(false)
@Argument(value = "-Xir-dce", description = "Perform experimental dead code elimination")
var irDce: Boolean by FreezableVar(false)
@Argument(value = "-Xir-only", description = "Disables pre-IR backend")
var irOnly: Boolean by FreezableVar(false)
@@ -205,10 +205,13 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
phaseConfig,
allDependencies = resolvedLibraries,
friendDependencies = friendDependencies,
mainArguments = mainCallArguments
mainArguments = mainCallArguments,
generateFullJs = !arguments.irDce,
generateDceJs = arguments.irDce
)
outputFile.writeText(compiledModule.jsCode)
val jsCode = if (arguments.irDce) compiledModule.dceJsCode!! else compiledModule.jsCode!!
outputFile.writeText(jsCode)
if (arguments.generateDts) {
val dtsFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "d.ts")!!
dtsFile.writeText(compiledModule.tsDefinitions ?: error("No ts definitions"))
@@ -0,0 +1,287 @@
/*
* 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
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.export.isExported
import org.jetbrains.kotlin.ir.backend.js.utils.getJsName
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockBodyImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.types.classifierOrFail
import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.*
import java.util.*
fun eliminateDeadDeclarations(
module: IrModuleFragment,
context: JsIrBackendContext,
mainFunction: IrSimpleFunction?
) {
val allRoots = buildRoots(module, context, mainFunction)
val usefulDeclarations = usefulDeclarations(allRoots, context)
removeUselessDeclarations(module, usefulDeclarations)
}
private fun buildRoots(module: IrModuleFragment, context: JsIrBackendContext, mainFunction: IrSimpleFunction?): Iterable<IrDeclaration> {
val rootDeclarations =
(module.files + context.packageLevelJsModules + context.externalPackageFragment.values).flatMapTo(mutableListOf()) { file ->
file.declarations.filter {
it is IrField && it.initializer != null && it.fqNameWhenAvailable?.asString()?.startsWith("kotlin") != true
|| it.isExported(context)
|| it.isEffectivelyExternal()
|| it is IrField && it.correspondingPropertySymbol?.owner?.isExported(context) == true
|| it is IrSimpleFunction && it.correspondingPropertySymbol?.owner?.isExported(context) == true
}
}
if (context.hasTests) rootDeclarations += context.testContainer
if (mainFunction != null) {
rootDeclarations += mainFunction
if (mainFunction.isSuspend) {
rootDeclarations += context.coroutineEmptyContinuation.owner
}
}
return rootDeclarations
}
private fun removeUselessDeclarations(module: IrModuleFragment, usefulDeclarations: Set<IrDeclaration>) {
module.files.forEach {
it.acceptVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitFile(declaration: IrFile) {
process(declaration)
}
override fun visitClass(declaration: IrClass) {
process(declaration)
}
override fun visitConstructor(declaration: IrConstructor) {
if (declaration !in usefulDeclarations) {
// Keep the constructor declaration without body in order to declare the JS constructor function
declaration.body = IrBlockBodyImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, emptyList())
}
}
private fun process(container: IrDeclarationContainer) {
container.declarations.transformFlat { member ->
if (member !in usefulDeclarations && member !is IrConstructor) {
emptyList()
} else {
member.acceptVoid(this)
null
}
}
}
})
}
}
private fun Iterable<IrDeclaration>.withNested(): Iterable<IrDeclaration> {
val result = mutableListOf<IrDeclaration>()
this.forEach {
it.acceptVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitBody(body: IrBody) {
// Skip
}
override fun visitDeclaration(declaration: IrDeclaration) {
super.visitDeclaration(declaration)
result += declaration
}
})
}
return result
}
fun usefulDeclarations(roots: Iterable<IrDeclaration>, context: JsIrBackendContext): Set<IrDeclaration> {
val queue = ArrayDeque<IrDeclaration>()
val result = hashSetOf<IrDeclaration>()
val constructedClasses = hashSetOf<IrClass>()
fun IrDeclaration.enqueue() {
if (this !in result) {
result.add(this)
queue.addLast(this)
}
}
// Add roots, including nested declarations
roots.withNested().forEach { it.enqueue() }
val toStringMethod =
context.irBuiltIns.anyClass.owner.declarations.filterIsInstance<IrFunction>().single { it.name.asString() == "toString" }
val equalsMethod =
context.irBuiltIns.anyClass.owner.declarations.filterIsInstance<IrFunction>().single { it.name.asString() == "equals" }
val hashCodeMethod =
context.irBuiltIns.anyClass.owner.declarations.filterIsInstance<IrFunction>().single { it.name.asString() == "hashCode" }
while (queue.isNotEmpty()) {
while (queue.isNotEmpty()) {
val declaration = queue.pollFirst()
if (declaration is IrClass) {
declaration.superTypes.forEach {
(it.classifierOrNull as? IrClassSymbol)?.owner?.enqueue()
}
// Special hack for `IntrinsicsJs.kt` support
if (declaration.superTypes.any { it.isSuspendFunctionTypeOrSubtype() }) {
declaration.declarations.forEach {
if (it is IrSimpleFunction && it.name.asString().startsWith("invoke")) {
it.enqueue()
}
}
}
// TODO find out how `doResume` gets removed
if (declaration.symbol == context.ir.symbols.coroutineImpl) {
declaration.declarations.forEach {
if (it is IrSimpleFunction && it.name.asString() == "doResume") {
it.enqueue()
}
}
}
}
if (declaration is IrSimpleFunction) {
declaration.resolveFakeOverride()?.enqueue()
}
// Collect instantiated classes.
if (declaration is IrConstructor) {
declaration.constructedClass.let {
it.enqueue()
constructedClasses += it
}
}
val body = when (declaration) {
is IrFunction -> declaration.body
is IrField -> declaration.initializer
is IrVariable -> declaration.initializer
else -> null
}
body?.acceptVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitFunctionAccess(expression: IrFunctionAccessExpression) {
super.visitFunctionAccess(expression)
expression.symbol.owner.enqueue()
}
override fun visitVariableAccess(expression: IrValueAccessExpression) {
super.visitVariableAccess(expression)
expression.symbol.owner.enqueue()
}
override fun visitFieldAccess(expression: IrFieldAccessExpression) {
super.visitFieldAccess(expression)
expression.symbol.owner.enqueue()
}
override fun visitCall(expression: IrCall) {
super.visitCall(expression)
when (expression.symbol) {
context.intrinsics.jsBoxIntrinsic -> {
val inlineClass = expression.getTypeArgument(0)!!.getInlinedClass()!!
val constructor = inlineClass.declarations.filterIsInstance<IrConstructor>().single { it.isPrimary }
constructor.enqueue()
}
context.intrinsics.jsClass -> {
(expression.getTypeArgument(0)!!.classifierOrFail.owner as IrDeclaration).enqueue()
}
context.intrinsics.jsObjectCreate.symbol -> {
val classToCreate = expression.getTypeArgument(0)!!.classifierOrFail.owner as IrClass
classToCreate.enqueue()
constructedClasses += classToCreate
}
context.intrinsics.jsEquals -> {
equalsMethod.enqueue()
}
context.intrinsics.jsToString -> {
toStringMethod.enqueue()
}
context.intrinsics.jsHashCode -> {
hashCodeMethod.enqueue()
}
context.intrinsics.jsPlus -> {
if (expression.getValueArgument(0)?.type?.classOrNull == context.irBuiltIns.stringClass) {
toStringMethod.enqueue()
}
}
}
}
override fun visitStringConcatenation(expression: IrStringConcatenation) {
super.visitStringConcatenation(expression)
toStringMethod.enqueue()
}
})
}
fun IrOverridableDeclaration<*>.overridesUsefulFunction(): Boolean {
return this.overriddenSymbols.any {
(it.owner as? IrOverridableDeclaration<*>)?.let {
it in result || it.overridesUsefulFunction()
} ?: false
}
}
for (klass in constructedClasses) {
for (declaration in klass.declarations) {
if (declaration in result) continue
if (declaration is IrOverridableDeclaration<*> && declaration.overridesUsefulFunction()) {
declaration.enqueue()
}
if (declaration is IrSimpleFunction && declaration.getJsNameOrKotlinName().asString() == "valueOf") {
declaration.enqueue()
}
// A hack to support `toJson` and other js-specific members
if (declaration.getJsName() != null ||
declaration is IrField && declaration.correspondingPropertySymbol?.owner?.getJsName() != null ||
declaration is IrSimpleFunction && declaration.correspondingPropertySymbol?.owner?.getJsName() != null
) {
declaration.enqueue()
}
}
}
}
return result
}
@@ -9,12 +9,19 @@ import com.intellij.openapi.project.Project
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.export.isExported
import org.jetbrains.kotlin.ir.backend.js.lower.moveBodilessDeclarationsToSeparatePlace
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer
import org.jetbrains.kotlin.ir.backend.js.utils.JsMainFunctionDetector
import org.jetbrains.kotlin.ir.backend.js.utils.NameTables
import org.jetbrains.kotlin.ir.backend.js.utils.isJsExport
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
import org.jetbrains.kotlin.ir.declarations.IrField
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.resolver.KotlinLibraryResolveResult
@@ -32,7 +39,8 @@ fun sortDependencies(dependencies: Collection<IrModuleFragment>): Collection<IrM
}
class CompilerResult(
val jsCode: String,
val jsCode: String?,
val dceJsCode: String?,
val tsDefinitions: String? = null
)
@@ -44,7 +52,9 @@ fun compile(
allDependencies: KotlinLibraryResolveResult,
friendDependencies: List<KotlinLibrary>,
mainArguments: List<String>?,
exportedDeclarations: Set<FqName> = emptySet()
exportedDeclarations: Set<FqName> = emptySet(),
generateFullJs: Boolean = true,
generateDceJs: Boolean = false
): CompilerResult {
val (moduleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer) =
loadIr(project, files, configuration, allDependencies, friendDependencies)
@@ -74,7 +84,7 @@ fun compile(
jsPhases.invokeToplevel(phaseConfig, context, moduleFragment)
val transformer = IrModuleToJsTransformer(context, mainFunction, mainArguments)
return transformer.generateModule(moduleFragment)
return transformer.generateModule(moduleFragment, generateFullJs, generateDceJs)
}
fun generateJsCode(
@@ -86,5 +96,5 @@ fun generateJsCode(
jsPhases.invokeToplevel(PhaseConfig(jsPhases), context, moduleFragment)
val transformer = IrModuleToJsTransformer(context, null, null, true, nameTables)
return transformer.generateModule(moduleFragment).jsCode
return transformer.generateModule(moduleFragment).jsCode!!
}
@@ -45,35 +45,9 @@ class ExportModelGenerator(val context: JsIrBackendContext) {
}
)
private fun getExportCandidate(declaration: IrDeclaration): IrDeclarationWithName? {
// Only actual public declarations with name can be exported
if (declaration !is IrDeclarationWithVisibility ||
declaration !is IrDeclarationWithName ||
declaration.visibility != Visibilities.PUBLIC ||
declaration.isExpect
) {
return null
}
// Workaround to get property declarations instead of its lowered accessors.
if (declaration is IrSimpleFunction) {
val property = declaration.correspondingPropertySymbol?.owner
if (property != null) {
// Return property for getter accessors only to prevent
// returning it twice (for getter and setter) in the same scope
return if (property.getter == declaration)
property
else
null
}
}
return declaration
}
private fun exportDeclaration(declaration: IrDeclaration): ExportedDeclaration? {
val candidate = getExportCandidate(declaration) ?: return null
if (!shouldDeclarationBeExported(candidate)) return null
if (!shouldDeclarationBeExported(candidate, context)) return null
return when (candidate) {
is IrSimpleFunction -> exportFunction(candidate)
@@ -173,7 +147,7 @@ class ExportModelGenerator(val context: JsIrBackendContext) {
for (declaration in klass.declarations) {
val candidate = getExportCandidate(declaration) ?: continue
if (!shouldDeclarationBeExported(candidate)) continue
if (!shouldDeclarationBeExported(candidate, context)) continue
when (candidate) {
is IrSimpleFunction ->
@@ -302,20 +276,6 @@ class ExportModelGenerator(val context: JsIrBackendContext) {
else identifier
}
private fun shouldDeclarationBeExported(declaration: IrDeclarationWithName): Boolean {
if (declaration.fqNameWhenAvailable in context.additionalExportedDeclarations)
return true
if (declaration.isJsExport())
return true
return when (val parent = declaration.parent) {
is IrDeclarationWithName -> shouldDeclarationBeExported(parent)
is IrAnnotationContainer -> parent.isJsExport()
else -> false
}
}
private fun functionExportability(function: IrSimpleFunction): Exportability {
if (function.isInline && function.typeParameters.any { it.isReified })
return Exportability.Prohibited("Inline reified function")
@@ -353,6 +313,51 @@ sealed class Exportability {
private val IrClassifierSymbol.isInterface
get() = (owner as? IrClass)?.isInterface == true
private fun getExportCandidate(declaration: IrDeclaration): IrDeclarationWithName? {
// Only actual public declarations with name can be exported
if (declaration !is IrDeclarationWithVisibility ||
declaration !is IrDeclarationWithName ||
declaration.visibility != Visibilities.PUBLIC ||
declaration.isExpect
) {
return null
}
// Workaround to get property declarations instead of its lowered accessors.
if (declaration is IrSimpleFunction) {
val property = declaration.correspondingPropertySymbol?.owner
if (property != null) {
// Return property for getter accessors only to prevent
// returning it twice (for getter and setter) in the same scope
return if (property.getter == declaration)
property
else
null
}
}
return declaration
}
private fun shouldDeclarationBeExported(declaration: IrDeclarationWithName, context: JsIrBackendContext): Boolean {
if (declaration.fqNameWhenAvailable in context.additionalExportedDeclarations)
return true
if (declaration.isJsExport())
return true
return when (val parent = declaration.parent) {
is IrDeclarationWithName -> shouldDeclarationBeExported(parent, context)
is IrAnnotationContainer -> parent.isJsExport()
else -> false
}
}
fun IrDeclaration.isExported(context: JsIrBackendContext): Boolean {
val candidate = getExportCandidate(this) ?: return false
return shouldDeclarationBeExported(candidate, context)
}
private val reservedWords = setOf(
"break",
"case",
@@ -138,6 +138,7 @@ class BridgesConstruction(val context: CommonBackendContext) : ClassLoweringPass
valueParameters += bridge.valueParameters.map { p -> p.copyTo(this) }
annotations += bridge.annotations
overriddenSymbols.addAll(delegateTo.overriddenSymbols)
overriddenSymbols.add(bridge.symbol)
}
context.createIrBuilder(irFunction.symbol).irBlockBody(irFunction) {
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.ir.backend.js.lower.calls
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.utils.OperatorNames
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
@@ -172,57 +173,70 @@ class NumberOperatorCallsTransformer(context: JsIrBackendContext) : CallsTransfo
else -> e
}
private fun withLongCoercion(default: (IrFunctionAccessExpression) -> IrExpression): (IrFunctionAccessExpression) -> IrExpression = { call ->
assert(call.valueArgumentsCount == 1)
val arg = call.getValueArgument(0)!!
private fun withLongCoercion(default: (IrFunctionAccessExpression) -> IrExpression): (IrFunctionAccessExpression) -> IrExpression =
{ call ->
assert(call.valueArgumentsCount == 1)
val arg = call.getValueArgument(0)!!
if (arg.type.isLong()) {
val receiverType = call.dispatchReceiver!!.type
var actualCall = call
when {
// Double OP Long => Double OP Long.toDouble()
receiverType.isDouble() -> {
call.putValueArgument(0, IrCallImpl(
call.startOffset,
call.endOffset,
intrinsics.longToDouble.owner.returnType,
intrinsics.longToDouble
).apply {
dispatchReceiver = arg
})
}
// Float OP Long => Float OP Long.toFloat()
receiverType.isFloat() -> {
call.putValueArgument(0, IrCallImpl(
call.startOffset,
call.endOffset,
intrinsics.longToFloat.owner.returnType,
intrinsics.longToFloat
).apply {
dispatchReceiver = arg
})
}
// {Byte, Short, Int} OP Long => {Byte, Sort, Int}.toLong() OP Long
!receiverType.isLong() -> {
call.dispatchReceiver = IrCallImpl(
call.startOffset,
call.endOffset,
intrinsics.jsNumberToLong.owner.returnType,
intrinsics.jsNumberToLong
).apply {
putValueArgument(0, call.dispatchReceiver)
if (arg.type.isLong()) {
val receiverType = call.dispatchReceiver!!.type
when {
// Double OP Long => Double OP Long.toDouble()
receiverType.isDouble() -> {
call.putValueArgument(0, IrCallImpl(
call.startOffset,
call.endOffset,
intrinsics.longToDouble.owner.returnType,
intrinsics.longToDouble
).apply {
dispatchReceiver = arg
})
}
// Float OP Long => Float OP Long.toFloat()
receiverType.isFloat() -> {
call.putValueArgument(0, IrCallImpl(
call.startOffset,
call.endOffset,
intrinsics.longToFloat.owner.returnType,
intrinsics.longToFloat
).apply {
dispatchReceiver = arg
})
}
// {Byte, Short, Int} OP Long => {Byte, Sort, Int}.toLong() OP Long
!receiverType.isLong() -> {
call.dispatchReceiver = IrCallImpl(
call.startOffset,
call.endOffset,
intrinsics.jsNumberToLong.owner.returnType,
intrinsics.jsNumberToLong
).apply {
putValueArgument(0, call.dispatchReceiver)
}
// Replace {Byte, Short, Int}.OP with corresponding Long.OP
val declaration = call.symbol.owner as IrSimpleFunction
val replacement = intrinsics.longClassSymbol.owner.declarations.filterIsInstance<IrSimpleFunction>()
.single { member ->
member.name.asString() == declaration.name.asString() &&
member.valueParameters.size == declaration.valueParameters.size &&
member.valueParameters.zip(declaration.valueParameters).all { (a, b) -> a.type == b.type }
}.symbol
actualCall = irCall(call, replacement)
}
}
}
}
if (call.dispatchReceiver!!.type.isLong()) {
// LHS is Long => use as is
call
} else {
default(call)
if (actualCall.dispatchReceiver!!.type.isLong()) {
actualCall
} else {
default(actualCall)
}
}
}
fun IrFunctionSymbol.call(vararg arguments: IrExpression) =
JsIrBuilder.buildCall(this, owner.returnType).apply {
@@ -8,8 +8,10 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.ir.backend.js.CompilerResult
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.eliminateDeadDeclarations
import org.jetbrains.kotlin.ir.backend.js.export.ExportModelGenerator
import org.jetbrains.kotlin.ir.backend.js.export.ExportModelToJsStatements
import org.jetbrains.kotlin.ir.backend.js.export.ExportedModule
import org.jetbrains.kotlin.ir.backend.js.export.toTypeScript
import org.jetbrains.kotlin.ir.backend.js.lower.StaticMembersLowering
import org.jetbrains.kotlin.ir.backend.js.utils.*
@@ -33,63 +35,7 @@ class IrModuleToJsTransformer(
private val moduleKind = backendContext.configuration[JSConfigurationKeys.MODULE_KIND]!!
private val generateRegionComments = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_REGION_COMMENTS)
private fun generateModuleBody(module: IrModuleFragment, context: JsGenerationContext): List<JsStatement> {
val statements = mutableListOf<JsStatement>().also {
if (!generateScriptModule) it += JsStringLiteral("use strict").makeStmt()
}
val preDeclarationBlock = JsGlobalBlock()
val postDeclarationBlock = JsGlobalBlock()
statements.addWithComment("block: pre-declaration", preDeclarationBlock)
val generateFilePaths = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_COMMENTS_WITH_FILE_PATH)
val pathPrefixMap = backendContext.configuration.getMap(JSConfigurationKeys.FILE_PATHS_PREFIX_MAP)
module.files.forEach {
val fileStatements = it.accept(IrFileToJsTransformer(), context).statements
if (fileStatements.isNotEmpty()) {
var startComment = ""
if (generateRegionComments) {
startComment = "region "
}
if (generateRegionComments || generateFilePaths) {
val originalPath = it.path
val path = pathPrefixMap.entries
.find { (k, _) -> originalPath.startsWith(k) }
?.let { (k, v) -> v + originalPath.substring(k.length) }
?: originalPath
startComment += "file: $path"
}
if (startComment.isNotEmpty()) {
statements.add(JsSingleLineComment(startComment))
}
statements.addAll(fileStatements)
statements.endRegion()
}
}
// sort member forwarding code
processClassModels(context.staticContext.classModels, preDeclarationBlock, postDeclarationBlock)
statements.addWithComment("block: post-declaration", postDeclarationBlock.statements)
statements.addWithComment("block: init", context.staticContext.initializerBlock.statements)
if (backendContext.hasTests) {
statements.startRegion("block: tests")
statements += JsInvocation(context.getNameForStaticFunction(backendContext.testContainer).makeRef()).makeStmt()
statements.endRegion()
}
return statements
}
fun generateModule(module: IrModuleFragment): CompilerResult {
fun generateModule(module: IrModuleFragment, fullJs: Boolean = true, dceJs: Boolean = false): CompilerResult {
val additionalPackages = with(backendContext) {
externalPackageFragment.values + listOf(
bodilessBuiltInsPackageFragment,
@@ -104,6 +50,17 @@ class IrModuleToJsTransformer(
namer.merge(module.files, additionalPackages)
val jsCode = if (fullJs) generateWrappedModuleBody(module, exportedModule) else null
val dceJsCode = if (dceJs) {
eliminateDeadDeclarations(module, backendContext, mainFunction)
generateWrappedModuleBody(module, exportedModule)
} else null
return CompilerResult(jsCode, dceJsCode, dts)
}
private fun generateWrappedModuleBody(module: IrModuleFragment, exportedModule: ExportedModule): String {
val program = JsProgram()
val nameGenerator = IrNamerImpl(
@@ -159,7 +116,63 @@ class IrModuleToJsTransformer(
)
}
return CompilerResult(program.toString(), dts)
return program.toString()
}
private fun generateModuleBody(module: IrModuleFragment, context: JsGenerationContext): List<JsStatement> {
val statements = mutableListOf<JsStatement>().also {
if (!generateScriptModule) it += JsStringLiteral("use strict").makeStmt()
}
val preDeclarationBlock = JsGlobalBlock()
val postDeclarationBlock = JsGlobalBlock()
statements.addWithComment("block: pre-declaration", preDeclarationBlock)
val generateFilePaths = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_COMMENTS_WITH_FILE_PATH)
val pathPrefixMap = backendContext.configuration.getMap(JSConfigurationKeys.FILE_PATHS_PREFIX_MAP)
module.files.forEach {
val fileStatements = it.accept(IrFileToJsTransformer(), context).statements
if (fileStatements.isNotEmpty()) {
var startComment = ""
if (generateRegionComments) {
startComment = "region "
}
if (generateRegionComments || generateFilePaths) {
val originalPath = it.path
val path = pathPrefixMap.entries
.find { (k, _) -> originalPath.startsWith(k) }
?.let { (k, v) -> v + originalPath.substring(k.length) }
?: originalPath
startComment += "file: $path"
}
if (startComment.isNotEmpty()) {
statements.add(JsSingleLineComment(startComment))
}
statements.addAll(fileStatements)
statements.endRegion()
}
}
// sort member forwarding code
processClassModels(context.staticContext.classModels, preDeclarationBlock, postDeclarationBlock)
statements.addWithComment("block: post-declaration", postDeclarationBlock.statements)
statements.addWithComment("block: init", context.staticContext.initializerBlock.statements)
if (backendContext.hasTests) {
statements.startRegion("block: tests")
statements += JsInvocation(context.getNameForStaticFunction(backendContext.testContainer).makeRef()).makeStmt()
statements.endRegion()
}
return statements
}
private fun generateMainArguments(mainFunction: IrSimpleFunction, rootContext: JsGenerationContext): List<JsExpression> {
@@ -6,10 +6,8 @@
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
import org.jetbrains.kotlin.ir.backend.js.export.isExported
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
@@ -84,22 +82,33 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
)
}
val getterRef = property.getter?.accessorRef()
val setterRef = property.setter?.accessorRef()
classBlock.statements += JsExpressionStatement(
defineProperty(
classPrototypeRef,
context.getNameForProperty(property).ident,
getter = getterRef,
setter = setterRef
if (irClass.isExported(context.staticContext.backendContext) ||
property.getter?.overridesExternal() == true ||
property.getJsName() != null
) {
val getterRef = property.getter?.accessorRef()
val setterRef = property.setter?.accessorRef()
classBlock.statements += JsExpressionStatement(
defineProperty(
classPrototypeRef,
context.getNameForProperty(property).ident,
getter = getterRef,
setter = setterRef
)
)
)
}
}
}
context.staticContext.classModels[irClass.symbol] = classModel
return classBlock
}
private fun IrSimpleFunction.overridesExternal(): Boolean {
if (this.isEffectivelyExternal()) return true
return this.overriddenSymbols.any { it.owner.overridesExternal() }
}
private fun generateMemberFunction(declaration: IrSimpleFunction): JsStatement? {
val translatedFunction = declaration.run { if (isReal) accept(IrFunctionToJsTransformer(), context) else null }
+1
View File
@@ -4,6 +4,7 @@ where advanced options include:
-Xfriend-modules=<path> Paths to friend modules
-Xfriend-modules-disabled Disable internal declaration export
-Xgenerate-dts Generate TypeScript declarations .d.ts file alongside JS file. Available in IR backend only.
-Xir-dce Perform experimental dead code elimination
-Xir-only Disables pre-IR backend
-Xir-produce-js Generates JS file using IR backend. Also disables pre-IR backend
-Xir-produce-klib-dir Generate unpacked KLIB into parent directory of output JS file.