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 2841ed056df..e86f9447da9 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 @@ -5,8 +5,7 @@ package org.jetbrains.kotlin.cli.common.arguments -import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants.CALL -import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants.NO_CALL +import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants.* import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.config.ApiVersion @@ -126,6 +125,13 @@ class K2JSCompilerArguments : CommonCompilerArguments() { @Argument(value = "-Xir-dce", description = "Perform experimental dead code elimination") var irDce: Boolean by FreezableVar(false) + @Argument( + value = "-Xir-dce-runtime-diagnostic", + valueDescription = "{$DCE_RUNTIME_DIAGNOSTIC_LOG|$DCE_RUNTIME_DIAGNOSTIC_EXCEPTION}", + description = "Enable runtime diagnostics when performing DCE instead of removing declarations" + ) + var irDceRuntimeDiagnostic: String? by NullableStringFreezableVar(null) + @Argument(value = "-Xir-dce-driven", description = "Perform a more experimental faster dead code elimination") var irDceDriven: Boolean by FreezableVar(false) diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JsArgumentConstants.java b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JsArgumentConstants.java index ea9a7d45ddd..822423006d4 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JsArgumentConstants.java +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JsArgumentConstants.java @@ -28,4 +28,7 @@ public interface K2JsArgumentConstants { String SOURCE_MAP_SOURCE_CONTENT_ALWAYS = "always"; String SOURCE_MAP_SOURCE_CONTENT_NEVER = "never"; String SOURCE_MAP_SOURCE_CONTENT_INLINING = "inlining"; + + String DCE_RUNTIME_DIAGNOSTIC_LOG = "log"; + String DCE_RUNTIME_DIAGNOSTIC_EXCEPTION = "exception"; } 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 9d08c4548bd..47209787023 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 @@ -17,6 +17,7 @@ import org.jetbrains.kotlin.cli.common.ExitCode.COMPILATION_ERROR import org.jetbrains.kotlin.cli.common.ExitCode.OK import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants +import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants.* import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoot import org.jetbrains.kotlin.cli.common.extensions.ScriptEvaluationExtension import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport @@ -259,12 +260,17 @@ class K2JsIrCompiler : CLICompiler() { mainArguments = mainCallArguments, generateFullJs = !arguments.irDce, generateDceJs = arguments.irDce, + dceRuntimeDiagnostic = DceRuntimeDiagnostic.resolve( + arguments.irDceRuntimeDiagnostic, + messageCollector + ), dceDriven = arguments.irDceDriven, multiModule = arguments.irPerModule, relativeRequirePath = true, propertyLazyInitialization = arguments.irPropertyLazyInitialization, ) + val jsCode = if (arguments.irDce && !arguments.irDceDriven) compiledModule.dceJsCode!! else compiledModule.jsCode!! outputFile.writeText(jsCode.mainModule) jsCode.dependencies.forEach { (name, content) -> @@ -422,6 +428,19 @@ class K2JsIrCompiler : CLICompiler() { } } +fun DceRuntimeDiagnostic.Companion.resolve( + value: String?, + messageCollector: MessageCollector +): DceRuntimeDiagnostic? = when (value?.toLowerCase()) { + DCE_RUNTIME_DIAGNOSTIC_LOG -> DceRuntimeDiagnostic.LOG + DCE_RUNTIME_DIAGNOSTIC_EXCEPTION -> DceRuntimeDiagnostic.EXCEPTION + null -> null + else -> { + messageCollector.report(STRONG_WARNING, "Unknown DCE runtime diagnostic '$value'") + null + } +} + fun messageCollectorLogger(collector: MessageCollector) = object : Logger { override fun warning(message: String) = collector.report(STRONG_WARNING, message) override fun error(message: String) = collector.report(ERROR, message) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt index 282cc71884e..564132fe6d7 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt @@ -7,7 +7,9 @@ package org.jetbrains.kotlin.ir.backend.js import org.jetbrains.kotlin.backend.common.ir.isMemberOfOpenClass 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.ir.JsIrBuilder import org.jetbrains.kotlin.ir.backend.js.utils.* import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* @@ -20,7 +22,9 @@ import org.jetbrains.kotlin.ir.util.* 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.config.DceRuntimeDiagnostic import org.jetbrains.kotlin.js.config.JSConfigurationKeys +import org.jetbrains.kotlin.js.config.removingBody import org.jetbrains.kotlin.utils.addIfNotNull import java.util.* @@ -34,7 +38,11 @@ fun eliminateDeadDeclarations( val usefulDeclarations = usefulDeclarations(allRoots, context) context.irFactory.stageController.unrestrictDeclarationListsAccess { - removeUselessDeclarations(modules, usefulDeclarations) + processUselessDeclarations( + modules, + usefulDeclarations, + context + ) } } @@ -47,7 +55,7 @@ private fun buildRoots(modules: Iterable, context: JsIrBackend (modules.flatMap { it.files } + context.packageLevelJsModules + context.externalPackageFragment.values).flatMapTo(mutableListOf()) { file -> file.declarations.flatMap { if (it is IrProperty) listOfNotNull(it.backingField, it.getter, it.setter) else listOf(it) } .filter { - it is IrField && it.initializer != null && it.fqNameWhenAvailable?.asString()?.startsWith("kotlin") != true + it is IrField && it.initializer != null && !it.isKotlinPackage() || it.isExported(context) || it.isEffectivelyExternal() || it is IrField && it.correspondingPropertySymbol?.owner?.isExported(context) == true @@ -57,6 +65,11 @@ private fun buildRoots(modules: Iterable, context: JsIrBackend rootDeclarations += context.testRoots.values + val dceRuntimeDiagnostic = context.dceRuntimeDiagnostic + if (dceRuntimeDiagnostic != null) { + rootDeclarations += dceRuntimeDiagnostic.unreachableDeclarationMethod(context).owner + } + JsMainFunctionDetector.getMainFunctionOrNull(modules.last())?.let { mainFunction -> rootDeclarations += mainFunction if (mainFunction.isSuspend) { @@ -67,7 +80,17 @@ private fun buildRoots(modules: Iterable, context: JsIrBackend return rootDeclarations } -private fun removeUselessDeclarations(modules: Iterable, usefulDeclarations: Set) { +private fun DceRuntimeDiagnostic.unreachableDeclarationMethod(context: JsIrBackendContext) = + when (this) { + DceRuntimeDiagnostic.LOG -> context.intrinsics.jsUnreachableDeclarationLog + DceRuntimeDiagnostic.EXCEPTION -> context.intrinsics.jsUnreachableDeclarationException + } + +private fun processUselessDeclarations( + modules: Iterable, + usefulDeclarations: Set, + context: JsIrBackendContext +) { modules.forEach { module -> module.files.forEach { it.acceptVoid(object : IrElementVisitorVoid { @@ -99,7 +122,7 @@ private fun removeUselessDeclarations(modules: Iterable, usefu private fun process(container: IrDeclarationContainer) { container.declarations.transformFlat { member -> if (member !in usefulDeclarations) { - emptyList() + member.processUselessDeclaration(context) } else { member.acceptVoid(this) null @@ -111,6 +134,53 @@ private fun removeUselessDeclarations(modules: Iterable, usefu } } +private fun IrDeclaration.processUselessDeclaration(context: JsIrBackendContext): List? { + return when { + context.dceRuntimeDiagnostic != null -> { + processWithDiagnostic(context) + return null + } + else -> emptyList() + } +} + +private fun IrDeclaration.processWithDiagnostic(context: JsIrBackendContext) { + when (this) { + is IrFunction -> processFunctionWithDiagnostic(context) + is IrField -> processFieldWithDiagnostic() + is IrDeclarationContainer -> declarations.forEach { it.processWithDiagnostic(context) } + } +} + +private fun IrFunction.processFunctionWithDiagnostic(context: JsIrBackendContext) { + val dceRuntimeDiagnostic = context.dceRuntimeDiagnostic!! + + val isRemovingBody = dceRuntimeDiagnostic.removingBody() + val targetMethod = dceRuntimeDiagnostic.unreachableDeclarationMethod(context) + val call = JsIrBuilder.buildCall( + target = targetMethod, + type = targetMethod.owner.returnType + ) + + if (isRemovingBody) { + body = context.irFactory.createBlockBody( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET + ) + } + + body?.prependFunctionCall(call) +} + +private fun IrField.processFieldWithDiagnostic() { + if (initializer != null && isKotlinPackage()) { + initializer = null + } +} + +private fun IrField.isKotlinPackage() = + fqNameWhenAvailable?.asString()?.startsWith("kotlin") == true + // TODO refactor it, the function became too big. Please contact me (Zalim) before doing it. fun usefulDeclarations(roots: Iterable, context: JsIrBackendContext): Set { val printReachabilityInfo = diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt index 6ba799b9622..4327be9078d 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt @@ -162,6 +162,9 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC val jsImul = getInternalFunction("imul") + val jsUnreachableDeclarationLog = getInternalFunction("unreachableDeclarationLog") + val jsUnreachableDeclarationException = getInternalFunction("unreachableDeclarationException") + // Coroutines val jsCoroutineContext 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 d0fd5431ce2..66dbd7d87b0 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 @@ -28,6 +28,7 @@ import org.jetbrains.kotlin.ir.symbols.impl.DescriptorlessExternalPackageFragmen import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.impl.IrDynamicTypeImpl import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.js.config.DceRuntimeDiagnostic import org.jetbrains.kotlin.js.config.ErrorTolerancePolicy import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.name.FqName @@ -44,6 +45,7 @@ class JsIrBackendContext( override val configuration: CompilerConfiguration, // TODO: remove configuration from backend context override val scriptMode: Boolean = false, override val es6mode: Boolean = false, + val dceRuntimeDiagnostic: DceRuntimeDiagnostic? = null, val propertyLazyInitialization: Boolean = false, override val irFactory: IrFactory = IrFactoryImpl ) : JsCommonBackendContext { 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 8801d53a744..1edf24909ab 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 @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.ir.declarations.persistent.PersistentIrFactory import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator import org.jetbrains.kotlin.ir.util.noUnboundLeft +import org.jetbrains.kotlin.js.config.DceRuntimeDiagnostic import org.jetbrains.kotlin.library.KotlinLibrary import org.jetbrains.kotlin.library.resolver.KotlinLibraryResolveResult import org.jetbrains.kotlin.name.FqName @@ -45,6 +46,7 @@ fun compile( generateFullJs: Boolean = true, generateDceJs: Boolean = false, dceDriven: Boolean = false, + dceRuntimeDiagnostic: DceRuntimeDiagnostic? = null, es6mode: Boolean = false, multiModule: Boolean = false, relativeRequirePath: Boolean = false, @@ -70,6 +72,7 @@ fun compile( exportedDeclarations, configuration, es6mode = es6mode, + dceRuntimeDiagnostic = dceRuntimeDiagnostic, propertyLazyInitialization = propertyLazyInitialization, irFactory = irFactory ) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PropertyLazyInitLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PropertyLazyInitLowering.kt index 2985400d775..6c5ca289b56 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PropertyLazyInitLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PropertyLazyInitLowering.kt @@ -14,6 +14,7 @@ import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.ir.JsIrArithBuilder import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder +import org.jetbrains.kotlin.ir.backend.js.utils.prependFunctionCall import org.jetbrains.kotlin.ir.util.isPure import org.jetbrains.kotlin.ir.builders.declarations.addFunction import org.jetbrains.kotlin.ir.builders.declarations.buildField @@ -74,7 +75,7 @@ class PropertyLazyInitLowering( when (container) { is IrSimpleFunction -> - irBody.addInitialization(initializationCall, container) + irBody.prependFunctionCall(initializationCall) is IrField -> { container .correspondingProperty @@ -83,7 +84,7 @@ class PropertyLazyInitLowering( ?.let { listOf(it.getter, it.setter) } ?.filterNotNull() ?.forEach { - irBody.addInitialization(initializationCall, it) + irBody.prependFunctionCall(initializationCall) } } } @@ -172,29 +173,6 @@ class PropertyLazyInitLowering( } } -private fun IrBody.addInitialization( - initCall: IrCall, - container: IrSimpleFunction -) { - when (this) { - is IrExpressionBody -> { - expression = JsIrBuilder.buildComposite( - type = container.returnType, - statements = listOf( - initCall, - expression - ) - ) - } - is IrBlockBody -> { - statements.add( - 0, - initCall - ) - } - } -} - private fun createIrGetField(field: IrField): IrGetField { return JsIrBuilder.buildGetField( symbol = field.symbol, diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/misc.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/misc.kt index 1cc9a52545f..c1daf564382 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/misc.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/misc.kt @@ -11,8 +11,9 @@ import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin import org.jetbrains.kotlin.ir.backend.js.export.isExported +import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder import org.jetbrains.kotlin.ir.declarations.* -import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl import org.jetbrains.kotlin.ir.expressions.impl.IrVarargImpl import org.jetbrains.kotlin.ir.types.IrType @@ -88,4 +89,26 @@ val IrValueDeclaration.isDispatchReceiver: Boolean if (parent is IrFunction && parent.dispatchReceiverParameter == this) return true return false - } \ No newline at end of file + } + +fun IrBody.prependFunctionCall( + call: IrCall +) { + when (this) { + is IrExpressionBody -> { + expression = JsIrBuilder.buildComposite( + type = expression.type, + statements = listOf( + call, + expression + ) + ) + } + is IrBlockBody -> { + statements.add( + 0, + call + ) + } + } +} \ No newline at end of file diff --git a/compiler/testData/cli/js/jsExtraHelp.out b/compiler/testData/cli/js/jsExtraHelp.out index b6134d27332..b67972707fb 100644 --- a/compiler/testData/cli/js/jsExtraHelp.out +++ b/compiler/testData/cli/js/jsExtraHelp.out @@ -11,6 +11,8 @@ where advanced options include: -Xir-dce-driven Perform a more experimental faster dead code elimination -Xir-dce-print-reachability-info Print declarations' reachability info to stdout during performing DCE + -Xir-dce-runtime-diagnostic={log|exception} + Enable runtime diagnostics when performing DCE instead of removing declarations -Xir-module-name= Specify a compilation module name for IR backend -Xir-only Disables pre-IR backend -Xir-per-module Splits generated .js per-module diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/config/DceRuntimeDiagnostic.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/config/DceRuntimeDiagnostic.kt new file mode 100644 index 00000000000..ee78d252589 --- /dev/null +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/config/DceRuntimeDiagnostic.kt @@ -0,0 +1,24 @@ +/* + * 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.js.config + +enum class DceRuntimeDiagnostic { + LOG, + EXCEPTION; + + companion object +} + +fun DceRuntimeDiagnostic.removingBody(): Boolean { + return this != DceRuntimeDiagnostic.LOG +} + +fun DceRuntimeDiagnostic.dceRuntimeDiagnosticToArgumentOfUnreachableMethod(): Int { + return when (this) { + DceRuntimeDiagnostic.LOG -> 0 + DceRuntimeDiagnostic.EXCEPTION -> 1 + } +} diff --git a/libraries/stdlib/js-ir-minimal-for-test/build.gradle.kts b/libraries/stdlib/js-ir-minimal-for-test/build.gradle.kts index 9084fcb3aee..c6ec3258978 100644 --- a/libraries/stdlib/js-ir-minimal-for-test/build.gradle.kts +++ b/libraries/stdlib/js-ir-minimal-for-test/build.gradle.kts @@ -71,7 +71,6 @@ val jsMainSources by task { "libraries/stdlib/js/src/kotlin/console.kt", "libraries/stdlib/js/src/kotlin/coreDeprecated.kt", "libraries/stdlib/js/src/kotlin/date.kt", - "libraries/stdlib/js/src/kotlin/debug.kt", "libraries/stdlib/js/src/kotlin/grouping.kt", "libraries/stdlib/js/src/kotlin/json.kt", "libraries/stdlib/js/src/kotlin/promise.kt", diff --git a/libraries/stdlib/js-ir/runtime/JsError.kt b/libraries/stdlib/js-ir/runtime/JsError.kt new file mode 100644 index 00000000000..e3e516decc1 --- /dev/null +++ b/libraries/stdlib/js-ir/runtime/JsError.kt @@ -0,0 +1,7 @@ +/* + * 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. + */ + +@JsName("Error") +internal open external class JsError(message: String) : Throwable \ No newline at end of file diff --git a/libraries/stdlib/js-ir/runtime/dceUtils.kt b/libraries/stdlib/js-ir/runtime/dceUtils.kt new file mode 100644 index 00000000000..70e29301ffa --- /dev/null +++ b/libraries/stdlib/js-ir/runtime/dceUtils.kt @@ -0,0 +1,16 @@ +/* + * 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 kotlin.js + +import JsError + +internal fun unreachableDeclarationLog() { + console.asDynamic().trace("Unreachable declaration") +} + +internal fun unreachableDeclarationException() { + throw JsError("Unreachable declaration") +} \ No newline at end of file