diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt index 1420c5e96b9..bab2cc67d67 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt @@ -100,6 +100,13 @@ class K2JSCompilerArguments : CommonCompilerArguments() { @Argument(value = "-target", valueDescription = "{ v5 }", description = "Generate JS files for specific ECMA version") var target: String? by NullableStringFreezableVar(null) + @Argument( + value = "-Xir-keep", + description = "Comma-separated list of fully-qualified names to not be eliminated by DCE (if it can be reached), " + + "and for which to keep non-minified names." + ) + var irKeep: String? by NullableStringFreezableVar(null) + @GradleOption( value = DefaultValues.JsModuleKinds::class, gradleInputType = GradleInputTypes.INPUT diff --git a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt index 0eb0c0da02e..1bda9adbc21 100644 --- a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt +++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt @@ -100,6 +100,10 @@ class K2JsIrCompiler : CLICompiler() { module, phaseConfig, irFactory, + keep = arguments.irKeep?.split(",") + ?.filterNot { it.isEmpty() } + ?.toSet() + ?: emptySet(), dceRuntimeDiagnostic = RuntimeDiagnostic.resolve( arguments.irDceRuntimeDiagnostic, messageCollector diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt index 0b7a384e001..3c877ce4828 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt @@ -66,6 +66,7 @@ class JsIrBackendContext( val symbolTable: SymbolTable, irModuleFragment: IrModuleFragment, val additionalExportedDeclarationNames: Set, + keep: Set, override val configuration: CompilerConfiguration, // TODO: remove configuration from backend context override val scriptMode: Boolean = false, override val es6mode: Boolean = false, @@ -86,6 +87,9 @@ class JsIrBackendContext( val minimizedNameGenerator: MinimizedNameGenerator = MinimizedNameGenerator() + val keeper: Keeper = + Keeper(keep) + val fieldDataCache = mutableMapOf>() override val builtIns = module.builtIns diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt index a7f9f961b72..e3dd9531fe4 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt @@ -51,6 +51,7 @@ fun compile( phaseConfig: PhaseConfig, irFactory: IrFactory, exportedDeclarations: Set = emptySet(), + keep: Set = emptySet(), dceRuntimeDiagnostic: RuntimeDiagnostic? = null, es6mode: Boolean = false, verifySignatures: Boolean = true, @@ -76,6 +77,7 @@ fun compile( deserializer, phaseConfig, exportedDeclarations, + keep, dceRuntimeDiagnostic, es6mode, baseClassIntoMetadata, @@ -97,6 +99,7 @@ fun compileIr( irLinker: JsIrLinker, phaseConfig: PhaseConfig, exportedDeclarations: Set, + keep: Set, dceRuntimeDiagnostic: RuntimeDiagnostic?, es6mode: Boolean, baseClassIntoMetadata: Boolean, @@ -119,6 +122,7 @@ fun compileIr( symbolTable, allModules.first(), exportedDeclarations, + keep, configuration, es6mode = es6mode, dceRuntimeDiagnostic = dceRuntimeDiagnostic, @@ -126,7 +130,7 @@ fun compileIr( safeExternalBoolean = safeExternalBoolean, safeExternalBooleanDiagnostic = safeExternalBooleanDiagnostic, granularity = granularity, - icCompatibleIr2Js = if (icCompatibleIr2Js) IcCompatibleIr2Js.COMPATIBLE else IcCompatibleIr2Js.DISABLED + icCompatibleIr2Js = if (icCompatibleIr2Js) IcCompatibleIr2Js.COMPATIBLE else IcCompatibleIr2Js.DISABLED, ) // Load declarations referenced during `context` initialization diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compilerWithIC.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compilerWithIC.kt index 4af9272cdd0..f43c70f0b39 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compilerWithIC.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compilerWithIC.kt @@ -32,6 +32,7 @@ fun compileWithIC( filesToLower: Collection, mainArguments: List? = null, exportedDeclarations: Set = emptySet(), + keep: Set = emptySet(), generateFullJs: Boolean = true, generateDceJs: Boolean = false, dceDriven: Boolean = false, @@ -42,7 +43,7 @@ fun compileWithIC( baseClassIntoMetadata: Boolean = false, lowerPerModule: Boolean = false, safeExternalBoolean: Boolean = false, - safeExternalBooleanDiagnostic: RuntimeDiagnostic? = null + safeExternalBooleanDiagnostic: RuntimeDiagnostic? = null, ): List { val irBuiltIns = mainModule.irBuiltins val symbolTable = (irBuiltIns as IrBuiltInsOverDescriptors).symbolTable @@ -53,13 +54,14 @@ fun compileWithIC( symbolTable, mainModule, exportedDeclarations, + keep, configuration, es6mode = es6mode, dceRuntimeDiagnostic = dceRuntimeDiagnostic, baseClassIntoMetadata = baseClassIntoMetadata, safeExternalBoolean = safeExternalBoolean, safeExternalBooleanDiagnostic = safeExternalBooleanDiagnostic, - icCompatibleIr2Js = IcCompatibleIr2Js.IC_MODE + icCompatibleIr2Js = IcCompatibleIr2Js.IC_MODE, ) // Load declarations referenced during `context` initialization diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/Dce.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/Dce.kt index 302175746bb..db15f4d54c6 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/Dce.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/Dce.kt @@ -57,6 +57,7 @@ private fun IrDeclaration.addRootsTo( getter?.addRootsTo(nestedVisitor, context) setter?.addRootsTo(nestedVisitor, context) } + isEffectivelyExternal() -> { val correspondingProperty = when (this) { is IrField -> correspondingPropertySymbol?.owner @@ -68,15 +69,18 @@ private fun IrDeclaration.addRootsTo( acceptVoid(nestedVisitor) } } + isExported(context) -> { acceptVoid(nestedVisitor) } + this is IrField -> { // TODO: simplify if ((initializer != null && !isKotlinPackage() || correspondingPropertySymbol?.owner?.isExported(context) == true) && !isConstant()) { acceptVoid(nestedVisitor) } } + this is IrSimpleFunction -> { val correspondingProperty = correspondingPropertySymbol?.owner ?: return if (correspondingProperty.isExported(context)) { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt index c7c0b9f3793..d506257c7b4 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt @@ -43,6 +43,7 @@ internal class JsUsefulDeclarationProcessor( val constructor = inlineClass.declarations.filterIsInstance().single { it.isPrimary } constructor.enqueue(data, "intrinsic: jsBoxIntrinsic") } + context.intrinsics.jsClass -> { val ref = expression.getTypeArgument(0)!!.classifierOrFail.owner as IrDeclaration ref.enqueue(data, "intrinsic: jsClass") @@ -63,34 +64,42 @@ internal class JsUsefulDeclarationProcessor( } } } + context.reflectionSymbols.getKClassFromExpression -> { val ref = expression.getTypeArgument(0)?.classOrNull ?: context.irBuiltIns.anyClass referencedJsClassesFromExpressions += ref.owner } + context.intrinsics.jsObjectCreateSymbol -> { val classToCreate = expression.getTypeArgument(0)!!.classifierOrFail.owner as IrClass classToCreate.enqueue(data, "intrinsic: jsObjectCreateSymbol") constructedClasses += classToCreate } + context.intrinsics.jsEquals -> { equalsMethod.enqueue(data, "intrinsic: jsEquals") } + context.intrinsics.jsToString -> { toStringMethod.enqueue(data, "intrinsic: jsToString") } + context.intrinsics.jsHashCode -> { hashCodeMethod.enqueue(data, "intrinsic: jsHashCode") } + context.intrinsics.jsPlus -> { if (expression.getValueArgument(0)?.type?.classOrNull == context.irBuiltIns.stringClass) { toStringMethod.enqueue(data, "intrinsic: jsPlus") } } + context.intrinsics.jsConstruct -> { val callType = expression.getTypeArgument(0)!! val constructor = callType.getClass()!!.primaryConstructor constructor!!.enqueue(data, "ctor call from jsConstruct-intrinsic") } + context.intrinsics.es6DefaultType -> { //same as jsClass val ref = expression.getTypeArgument(0)!!.classifierOrFail.owner as IrDeclaration @@ -104,6 +113,7 @@ internal class JsUsefulDeclarationProcessor( constructedClasses.add(klass) } } + context.intrinsics.jsInvokeSuspendSuperType, context.intrinsics.jsInvokeSuspendSuperTypeWithReceiver, context.intrinsics.jsInvokeSuspendSuperTypeWithReceiverAndParam -> { @@ -126,6 +136,14 @@ internal class JsUsefulDeclarationProcessor( override fun processClass(irClass: IrClass) { super.processClass(irClass) + if (context.keeper.shouldKeep(irClass)) { + irClass.declarations + .filter { context.keeper.shouldKeep(it) } + .forEach { declaration -> + declaration.enqueue(irClass, "kept declaration") + } + } + if (irClass.containsMetadata()) { when { irClass.isObject -> context.intrinsics.metadataObjectConstructorSymbol.owner.enqueue(irClass, "object metadata") diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt index 0dff12e6cad..efbfbf96af2 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformerTmp.kt @@ -114,6 +114,18 @@ class IrModuleToJsTransformerTmp( } private fun doStaticMembersLowering(modules: Iterable) { + modules.forEach { module -> + module.files.forEach { + it.accept( + backendContext.keeper, + Keeper.KeepData( + classInKeep = false, + classShouldBeKept = false + ) + ) + } + } + modules.forEach { module -> module.files.forEach { StaticMembersLowering(backendContext).lower(it) } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsNameLinkingNamer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsNameLinkingNamer.kt index fb045068918..cce4037cd1f 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsNameLinkingNamer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsNameLinkingNamer.kt @@ -86,10 +86,15 @@ class JsNameLinkingNamer(private val context: JsIrBackendContext, private val mi override fun getNameForMemberFunction(function: IrSimpleFunction): JsName { require(function.dispatchReceiverParameter != null) val signature = jsFunctionSignature(function, context) + if (context.keeper.shouldKeep(function)) { + context.minimizedNameGenerator.keepName(signature) + } val result = if (minimizedMemberNames && !function.hasStableJsName(context)) { function.parentAsClass.fieldData() context.minimizedNameGenerator.nameBySignature(signature) - } else signature + } else { + signature + } return result.toJsName() } @@ -125,6 +130,7 @@ class JsNameLinkingNamer(private val context: JsIrBackendContext, private val mi context.minimizedNameGenerator.reserveName(signature) } } + declaration is IrProperty -> { if (declaration.isExported(context)) { context.minimizedNameGenerator.reserveName(declaration.getJsNameOrKotlinName().identifier) @@ -146,7 +152,7 @@ class JsNameLinkingNamer(private val context: JsIrBackendContext, private val mi correspondingProperty.isSimpleProperty val safeName = when { hasStableName -> (correspondingProperty ?: it).getJsNameOrKotlinName().identifier - minimizedMemberNames -> context.minimizedNameGenerator.generateNextName() + minimizedMemberNames && !context.keeper.shouldKeep(it) -> context.minimizedNameGenerator.generateNextName() else -> it.safeName() } val resultName = if (!hasStableName) { @@ -156,6 +162,7 @@ class JsNameLinkingNamer(private val context: JsIrBackendContext, private val mi } else safeName result[it] = resultName } + it is IrFunction && it.dispatchReceiverParameter != null -> { nameCnt[jsFunctionSignature(it, context)] = 1 // avoid clashes with member functions } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/Keeper.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/Keeper.kt new file mode 100644 index 00000000000..c4ede90608c --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/Keeper.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2010-2022 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.declarations.* +import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable +import org.jetbrains.kotlin.ir.visitors.IrElementVisitor + +class Keeper(private val keep: Set) : IrElementVisitor { + private val keptDeclarations: MutableSet = mutableSetOf() + + fun shouldKeep(declaration: IrDeclaration): Boolean { + return declaration in keptDeclarations + } + + override fun visitElement(element: IrElement, data: KeepData) { + element.acceptChildren(this, data) + } + + /** Keep declarations can work both ways + * if member of a class is in keep, the class should be also kept + * if a class is kept, members of the class should be also kept + * but there can be nested classes, and for nested classes we need only to propagate "keep" from top-level to nested (not vice versa) + * because we have 2 directions, we need 2 boolean flags + * [KeepData.classInKeep] responsible to propagate "keep" from class level to members direction + * [KeepData.classShouldBeKept] responsible to bubble "keep" from members to class level direction + */ + override fun visitClass(declaration: IrClass, data: KeepData) { + val prevShouldBeKept = data.classShouldBeKept + val prevClassInKeep = data.classInKeep + data.classShouldBeKept = false + val keptClass = data.classInKeep || isInKeep(declaration) + if (keptClass) { + keptDeclarations.add(declaration) + } + data.classInKeep = keptClass + super.visitClass(declaration, data) + if (data.classShouldBeKept) { + keptDeclarations.add(declaration) + } + data.classShouldBeKept = prevShouldBeKept + data.classInKeep = prevClassInKeep + } + + override fun visitDeclaration(declaration: IrDeclarationBase, data: KeepData) { + super.visitDeclaration(declaration, data) + if (declaration in keptDeclarations) { + return + } + if (declaration is IrDeclarationWithName && isInKeep(declaration) || data.classInKeep) { + keptDeclarations.add(declaration) + data.classShouldBeKept = true + return + } + } + + private fun isInKeep(declaration: IrDeclarationWithName): Boolean { + return declaration.fqNameWhenAvailable?.asString() in keep + } + + class KeepData(var classInKeep: Boolean, var classShouldBeKept: Boolean) +} diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/MinimizedNameGenerator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/MinimizedNameGenerator.kt index d27218d267f..f537b2fbb56 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/MinimizedNameGenerator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/MinimizedNameGenerator.kt @@ -9,6 +9,7 @@ class MinimizedNameGenerator { private var index = 0 private val functionSignatureToName = mutableMapOf() private val reservedNames = mutableSetOf() + private val keptNames = mutableSetOf() fun generateNextName(): String { var candidate = index++.toJsIdentifier() @@ -19,11 +20,16 @@ class MinimizedNameGenerator { } fun nameBySignature(signature: String): String { + if (signature in keptNames) return signature return functionSignatureToName.getOrPut(signature) { generateNextName() } } + fun keepName(signature: String): Boolean { + return keptNames.add(signature) + } + fun reserveName(signature: String) { reservedNames.add(signature) } diff --git a/compiler/testData/cli/js/jsExtraHelp.out b/compiler/testData/cli/js/jsExtraHelp.out index f98b6b8221c..70fc4ce0c68 100644 --- a/compiler/testData/cli/js/jsExtraHelp.out +++ b/compiler/testData/cli/js/jsExtraHelp.out @@ -19,6 +19,7 @@ where advanced options include: Enable runtime diagnostics when performing DCE instead of removing declarations -Xir-generate-inline-anonymous-functions Lambda expressions that capture values are translated into in-line anonymous JavaScript functions + -Xir-keep Comma-separated list of fully-qualified names to not be eliminated by DCE (if it can be reached), and for which to keep non-minified names. -Xir-minimized-member-names Perform minimization for names of members -Xir-module-name= Specify a compilation module name for IR backend -Xir-new-ir2js New fragment-based ir2js diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/JsEnvironmentConfigurationDirectives.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/JsEnvironmentConfigurationDirectives.kt index 1fa1529fb49..05bc1769bc2 100644 --- a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/JsEnvironmentConfigurationDirectives.kt +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/JsEnvironmentConfigurationDirectives.kt @@ -210,6 +210,11 @@ object JsEnvironmentConfigurationDirectives : SimpleDirectivesContainer() { applicability = DirectiveApplicability.Global ) + val ONLY_IR_DCE by directive( + description = "Disable non DCE build", + applicability = DirectiveApplicability.Global + ) + val RUN_IC by directive( description = "", applicability = DirectiveApplicability.Global @@ -251,4 +256,9 @@ object JsEnvironmentConfigurationDirectives : SimpleDirectivesContainer() { """.trimIndent(), applicability = DirectiveApplicability.Global, ) + + val KEEP by stringDirective( + description = "Keep declarations", + applicability = DirectiveApplicability.Global + ) } diff --git a/js/js.tests/test/org/jetbrains/kotlin/benchmarks/GenerateIrRuntime.kt b/js/js.tests/test/org/jetbrains/kotlin/benchmarks/GenerateIrRuntime.kt index 94cee37f452..11714f5bfbc 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/benchmarks/GenerateIrRuntime.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/benchmarks/GenerateIrRuntime.kt @@ -593,7 +593,15 @@ class GenerateIrRuntime { private fun doBackEnd( module: IrModuleFragment, symbolTable: SymbolTable, irBuiltIns: IrBuiltIns, jsLinker: JsIrLinker ): CompilerResult { - val context = JsIrBackendContext(module.descriptor, irBuiltIns, symbolTable, module, emptySet(), configuration) + val context = JsIrBackendContext( + module.descriptor, + irBuiltIns, + symbolTable, + module, + additionalExportedDeclarationNames = emptySet(), + keep = emptySet(), + configuration + ) ExternalDependenciesGenerator(symbolTable, listOf(jsLinker)).generateUnboundSymbolsAsDependencies() diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrBackendFacade.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrBackendFacade.kt index c642ae6bbe5..3450ee6c0d2 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrBackendFacade.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/converters/JsIrBackendFacade.kt @@ -81,6 +81,7 @@ class JsIrBackendFacade( val splitPerFile = JsEnvironmentConfigurationDirectives.SPLIT_PER_FILE in module.directives val perModule = JsEnvironmentConfigurationDirectives.PER_MODULE in module.directives val runNewIr2Js = JsEnvironmentConfigurationDirectives.RUN_NEW_IR_2_JS in module.directives + val keep = module.directives[JsEnvironmentConfigurationDirectives.KEEP].toSet() val granularity = when { !firstTimeCompilation -> JsGenerationGranularity.WHOLE_PROGRAM @@ -145,6 +146,7 @@ class JsIrBackendFacade( deserializer, phaseConfig, exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, TEST_FUNCTION))), + keep = keep, dceRuntimeDiagnostic = null, es6mode = false, baseClassIntoMetadata = false, @@ -166,6 +168,7 @@ class JsIrBackendFacade( val mainArguments = JsEnvironmentConfigurator.getMainCallParametersForModule(module) .run { if (shouldBeGenerated()) arguments() else null } val runIrDce = JsEnvironmentConfigurationDirectives.RUN_IR_DCE in module.directives + val onlyIrDce = JsEnvironmentConfigurationDirectives.ONLY_IR_DCE in module.directives val esModules = JsEnvironmentConfigurationDirectives.ES_MODULES in module.directives val runNewIr2Js = JsEnvironmentConfigurationDirectives.RUN_NEW_IR_2_JS in module.directives val perModuleOnly = JsEnvironmentConfigurationDirectives.SPLIT_PER_MODULE in module.directives @@ -180,7 +183,7 @@ class JsIrBackendFacade( // If perModuleOnly then skip whole program // (it.dce => runIrDce) && (perModuleOnly => it.perModule) val translationModes = TranslationMode.values() - .filter { (!it.dce || runIrDce) && (!perModuleOnly || it.perModule) } + .filter { (it.dce || !onlyIrDce) && (!it.dce || runIrDce) && (!perModuleOnly || it.perModule) } .filter { it.dce == it.minimizedMemberNames } .toSet() val compilationOut = transformer.generateModule(loweredIr.allModules, translationModes, false) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsMinifierRunner.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsMinifierRunner.kt index 163f8ce1b3c..e7841a52c28 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsMinifierRunner.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/JsMinifierRunner.kt @@ -33,8 +33,9 @@ class JsMinifierRunner(testServices: TestServices) : AbstractJsArtifactsCollecto val dontRunGeneratedCode = globalDirectives[JsEnvironmentConfigurationDirectives.DONT_RUN_GENERATED_CODE] .contains(testServices.defaultsProvider.defaultTargetBackend?.name) val esModules = JsEnvironmentConfigurationDirectives.ES_MODULES in globalDirectives + val onlyIrDce = JsEnvironmentConfigurationDirectives.ONLY_IR_DCE in globalDirectives - if (dontRunGeneratedCode || esModules) return + if (dontRunGeneratedCode || esModules || onlyIrDce) return val allJsFiles = getOnlyJsFilesForRunner(testServices, modulesToArtifact) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/NodeJsGeneratorHandler.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/NodeJsGeneratorHandler.kt index 1fd01de8cbb..3ea13337dea 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/NodeJsGeneratorHandler.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/handlers/NodeJsGeneratorHandler.kt @@ -22,7 +22,6 @@ import java.io.File class NodeJsGeneratorHandler(testServices: TestServices) : AbstractJsArtifactsCollector(testServices) { override fun processAfterAllModules(someAssertionWasFailed: Boolean) { if (someAssertionWasFailed) return - val allJsFiles = getOnlyJsFilesForRunner(testServices, modulesToArtifact) val globalDirectives = testServices.moduleStructure.allDirectives @@ -31,8 +30,11 @@ class NodeJsGeneratorHandler(testServices: TestServices) : AbstractJsArtifactsCo val generateNodeJsRunner = JsEnvironmentConfigurationDirectives.GENERATE_NODE_JS_RUNNER in globalDirectives val skipNodeJs = JsEnvironmentConfigurationDirectives.SKIP_NODE_JS in globalDirectives val esModules = JsEnvironmentConfigurationDirectives.ES_MODULES in globalDirectives + val onlyIrDce = JsEnvironmentConfigurationDirectives.ONLY_IR_DCE in globalDirectives - if (dontRunGeneratedCode || !generateNodeJsRunner || skipNodeJs || esModules) return + if (dontRunGeneratedCode || !generateNodeJsRunner || skipNodeJs || esModules || onlyIrDce) return + + val allJsFiles = getOnlyJsFilesForRunner(testServices, modulesToArtifact) val mainModuleName = getMainModuleName(testServices) val outputDir = File(JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices).absolutePath) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/RunnerUtils.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/RunnerUtils.kt index 1dbfcdee00c..0bc11633cab 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/RunnerUtils.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/RunnerUtils.kt @@ -136,10 +136,14 @@ fun getAllFilesForRunner( val artifactsPaths = modulesToArtifact.values.map { it.outputFile.absolutePath }.filter { !File(it).isDirectory } val allJsFiles = additionalFiles + inputJsFilesBefore +artifactsPaths + commonFiles + additionalMainFiles + inputJsFilesAfter - val result = mutableMapOf(TranslationMode.FULL to allJsFiles) + val result = mutableMapOf>() val globalDirectives = testServices.moduleStructure.allDirectives val runIrDce = JsEnvironmentConfigurationDirectives.RUN_IR_DCE in globalDirectives + val onlyIrDce = JsEnvironmentConfigurationDirectives.ONLY_IR_DCE in globalDirectives + if (!onlyIrDce) { + result[TranslationMode.FULL] = allJsFiles + } if (runIrDce) { val dceJsFiles = artifactsPaths.map { it.replace(outputDir.absolutePath, dceOutputDir.absolutePath) } val dceAllJsFiles = additionalFiles + inputJsFilesBefore + dceJsFiles + commonFiles + additionalMainFiles + inputJsFilesAfter diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java index 099cc5c62ab..1828c99127e 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java @@ -6797,6 +6797,16 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { } } + @Nested + @TestMetadata("js/js.translator/testData/box/keep") + @TestDataPath("$PROJECT_ROOT") + public class Keep { + @Test + public void testAllFilesPresentInKeep() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/keep"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true); + } + } + @Nested @TestMetadata("js/js.translator/testData/box/kotlin.test") @TestDataPath("$PROJECT_ROOT") diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/FirJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/FirJsTestGenerated.java index d8388a77aa0..6c7998246a8 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/FirJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/FirJsTestGenerated.java @@ -7269,6 +7269,46 @@ public class FirJsTestGenerated extends AbstractFirJsTest { } } + @Nested + @TestMetadata("js/js.translator/testData/box/keep") + @TestDataPath("$PROJECT_ROOT") + public class Keep { + @Test + public void testAllFilesPresentInKeep() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/keep"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true); + } + + @Test + @TestMetadata("keepClass.kt") + public void testKeepClass() throws Exception { + runTest("js/js.translator/testData/box/keep/keepClass.kt"); + } + + @Test + @TestMetadata("keepMethod.kt") + public void testKeepMethod() throws Exception { + runTest("js/js.translator/testData/box/keep/keepMethod.kt"); + } + + @Test + @TestMetadata("keepNestedClass.kt") + public void testKeepNestedClass() throws Exception { + runTest("js/js.translator/testData/box/keep/keepNestedClass.kt"); + } + + @Test + @TestMetadata("keepNestedClassIfKeptTopLevelClass.kt") + public void testKeepNestedClassIfKeptTopLevelClass() throws Exception { + runTest("js/js.translator/testData/box/keep/keepNestedClassIfKeptTopLevelClass.kt"); + } + + @Test + @TestMetadata("keepOverriddenMethod.kt") + public void testKeepOverriddenMethod() throws Exception { + runTest("js/js.translator/testData/box/keep/keepOverriddenMethod.kt"); + } + } + @Nested @TestMetadata("js/js.translator/testData/box/kotlin.test") @TestDataPath("$PROJECT_ROOT") diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java index 3e474f90a51..1f9efe60d74 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java @@ -7269,6 +7269,46 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { } } + @Nested + @TestMetadata("js/js.translator/testData/box/keep") + @TestDataPath("$PROJECT_ROOT") + public class Keep { + @Test + public void testAllFilesPresentInKeep() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/keep"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true); + } + + @Test + @TestMetadata("keepClass.kt") + public void testKeepClass() throws Exception { + runTest("js/js.translator/testData/box/keep/keepClass.kt"); + } + + @Test + @TestMetadata("keepMethod.kt") + public void testKeepMethod() throws Exception { + runTest("js/js.translator/testData/box/keep/keepMethod.kt"); + } + + @Test + @TestMetadata("keepNestedClass.kt") + public void testKeepNestedClass() throws Exception { + runTest("js/js.translator/testData/box/keep/keepNestedClass.kt"); + } + + @Test + @TestMetadata("keepNestedClassIfKeptTopLevelClass.kt") + public void testKeepNestedClassIfKeptTopLevelClass() throws Exception { + runTest("js/js.translator/testData/box/keep/keepNestedClassIfKeptTopLevelClass.kt"); + } + + @Test + @TestMetadata("keepOverriddenMethod.kt") + public void testKeepOverriddenMethod() throws Exception { + runTest("js/js.translator/testData/box/keep/keepOverriddenMethod.kt"); + } + } + @Nested @TestMetadata("js/js.translator/testData/box/kotlin.test") @TestDataPath("$PROJECT_ROOT") diff --git a/js/js.translator/testData/box/keep/keepClass.kt b/js/js.translator/testData/box/keep/keepClass.kt new file mode 100644 index 00000000000..4f08f0c43e9 --- /dev/null +++ b/js/js.translator/testData/box/keep/keepClass.kt @@ -0,0 +1,33 @@ +// TARGET_BACKEND: JS_IR +// ONLY_IR_DCE +// RUN_PLAIN_BOX_FUNCTION +// INFER_MAIN_MODULE +// KEEP: A + +// MODULE: keep_class +// FILE: lib.kt + +class A { + fun foo(): String { + return "foo" + } + + fun bar(): String { + return "bar" + } +} + +@JsExport +fun bar(): A { + return A() +} + +// FILE: test.js +function box() { + var a = this["keep_class"].bar() + + if (a.foo_26di_k$() != "foo") return "fail 1" + if (a.bar_232r_k$() != "bar") return "fail 2" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/keep/keepMethod.kt b/js/js.translator/testData/box/keep/keepMethod.kt new file mode 100644 index 00000000000..2634a85fcc1 --- /dev/null +++ b/js/js.translator/testData/box/keep/keepMethod.kt @@ -0,0 +1,33 @@ +// TARGET_BACKEND: JS_IR +// ONLY_IR_DCE +// RUN_PLAIN_BOX_FUNCTION +// INFER_MAIN_MODULE +// KEEP: A.foo + +// MODULE: keep_method +// FILE: lib.kt + +class A { + fun foo(): String { + return "foo" + } + + fun bar(): String { + return "bar" + } +} + +@JsExport +fun bar(): A { + return A() +} + +// FILE: test.js +function box() { + var a = this["keep_method"].bar() + + if (a.foo_26di_k$() != "foo") return "fail 1" + if (typeof a.bar_232r_k$ !== "undefined") return "fail 2" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/keep/keepNestedClass.kt b/js/js.translator/testData/box/keep/keepNestedClass.kt new file mode 100644 index 00000000000..4bfbab37f70 --- /dev/null +++ b/js/js.translator/testData/box/keep/keepNestedClass.kt @@ -0,0 +1,44 @@ +// TARGET_BACKEND: JS_IR +// ONLY_IR_DCE +// RUN_PLAIN_BOX_FUNCTION +// INFER_MAIN_MODULE +// KEEP: A.B + +// MODULE: keep_nested_class +// FILE: lib.kt + +class A { + fun foo(): String { + return "foo" + } + + fun bar(): String { + return "bar" + } + + class B { + fun baz() = "baz" + } +} + +@JsExport +fun barA(): A { + return A() +} + +@JsExport +fun bar(): A.B { + return A.B() +} + +// FILE: test.js +function box() { + var a = this["keep_nested_class"].barA() + var b = this["keep_nested_class"].bar() + + if (typeof a.foo_26di_k$ !== "undefined") return "fail 1" + if (typeof a.bar_232r_k$ !== "undefined") return "fail 2" + if (b.baz_232z_k$() != "baz") return "fail 3" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/keep/keepNestedClassIfKeptTopLevelClass.kt b/js/js.translator/testData/box/keep/keepNestedClassIfKeptTopLevelClass.kt new file mode 100644 index 00000000000..61f7b6e1355 --- /dev/null +++ b/js/js.translator/testData/box/keep/keepNestedClassIfKeptTopLevelClass.kt @@ -0,0 +1,44 @@ +// TARGET_BACKEND: JS_IR +// ONLY_IR_DCE +// RUN_PLAIN_BOX_FUNCTION +// INFER_MAIN_MODULE +// KEEP: A + +// MODULE: keep_nested_class_if_kept_top_level_class +// FILE: lib.kt + +class A { + fun foo(): String { + return "foo" + } + + fun bar(): String { + return "bar" + } + + class B { + fun baz() = "baz" + } +} + +@JsExport +fun barA(): A { + return A() +} + +@JsExport +fun bar(): A.B { + return A.B() +} + +// FILE: test.js +function box() { + var a = this["keep_nested_class_if_kept_top_level_class"].barA() + var b = this["keep_nested_class_if_kept_top_level_class"].bar() + + if (a.foo_26di_k$() != "foo") return "fail 1" + if (a.bar_232r_k$() != "bar") return "fail 2" + if (b.baz_232z_k$() != "baz") return "fail 3" + + return "OK" +} \ No newline at end of file diff --git a/js/js.translator/testData/box/keep/keepOverriddenMethod.kt b/js/js.translator/testData/box/keep/keepOverriddenMethod.kt new file mode 100644 index 00000000000..07a3f6ac273 --- /dev/null +++ b/js/js.translator/testData/box/keep/keepOverriddenMethod.kt @@ -0,0 +1,38 @@ +// TARGET_BACKEND: JS_IR +// ONLY_IR_DCE +// RUN_PLAIN_BOX_FUNCTION +// INFER_MAIN_MODULE +// KEEP: A.foo + +// MODULE: keep_overridden_method +// FILE: lib.kt + +open class A { + open fun foo(): String { + return "foo" + } + + open fun bar(): String { + return "bar" + } +} + +open class B : A() { + override fun foo(): String { + return super.foo() + "!" + } +} + +@JsExport +fun bar(): B { + return B() +} + +// FILE: test.js +function box() { + var b = this["keep_overridden_method"].bar() + + if (b.foo_26di_k$() != "foo!") return "fail 1" + + return "OK" +} \ No newline at end of file diff --git a/plugins/scripting/scripting-compiler-js/src/org/jetbrains/kotlin/scripting/js/JsCoreScriptingCompiler.kt b/plugins/scripting/scripting-compiler-js/src/org/jetbrains/kotlin/scripting/js/JsCoreScriptingCompiler.kt index 08521f2c826..62b5ce2a33c 100644 --- a/plugins/scripting/scripting-compiler-js/src/org/jetbrains/kotlin/scripting/js/JsCoreScriptingCompiler.kt +++ b/plugins/scripting/scripting-compiler-js/src/org/jetbrains/kotlin/scripting/js/JsCoreScriptingCompiler.kt @@ -92,7 +92,8 @@ class JsCoreScriptingCompiler( psi2irContext.irBuiltIns, psi2irContext.symbolTable, irModuleFragment, - emptySet(), + additionalExportedDeclarationNames = emptySet(), + keep = emptySet(), environment.configuration, true ) diff --git a/plugins/scripting/scripting-compiler-js/src/org/jetbrains/kotlin/scripting/js/JsScriptDependencyCompiler.kt b/plugins/scripting/scripting-compiler-js/src/org/jetbrains/kotlin/scripting/js/JsScriptDependencyCompiler.kt index afcababa996..783661b205c 100644 --- a/plugins/scripting/scripting-compiler-js/src/org/jetbrains/kotlin/scripting/js/JsScriptDependencyCompiler.kt +++ b/plugins/scripting/scripting-compiler-js/src/org/jetbrains/kotlin/scripting/js/JsScriptDependencyCompiler.kt @@ -62,7 +62,8 @@ class JsScriptDependencyCompiler( irBuiltIns, symbolTable, moduleFragment, - emptySet(), + additionalExportedDeclarationNames = emptySet(), + keep = emptySet(), configuration, true )