[JS IR] Extract adding of function call to another function

[JS IR] Add option for dce mode

[JS IR] Add logging to non useful declarations if appropriate dce mode

[JS IR] Add mode with throwing exception

[JS IR] unreachableDeclaration method is in rootDeclarations

[JS IR] Add js extra help arg with dce mode and include debug.kt to compile unreachableMethod

[JS IR] unreachableDeclaration as internal to not reproduce stdlib api

[JS IR] Fix description of dce mode argument

- Use console.error instead of console.log
- Use JsError instead Kotlin exception for lightweight

[JS IR] Remove body for throwing exception

[JS IR] Remove default parameter in unreachableDeclaration

[JS IR] Process without removing fields and declaration containers

[JS IR] Rename dce mode on dce runtime diagnostic

[JS IR] Use console.trace instead of console.error

[JS IR] Extract JsError

- Fix naming in prependFunctionCall
- Fix description on runtime diagnostic argument
- Using message collector instead of throwing exception

[JS IR] Distinguish unreachableMethods for log and exception

[JS IR] Extract checking of Kotlin packages of IrField

^KT-45059 fixed
This commit is contained in:
Ilya Goncharov
2021-02-01 16:22:07 +03:00
parent 5568ceef68
commit bd2601f289
14 changed files with 189 additions and 34 deletions
@@ -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)
@@ -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";
}
@@ -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<K2JSCompilerArguments>() {
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<K2JSCompilerArguments>() {
}
}
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)
@@ -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<IrModuleFragment>, 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<IrModuleFragment>, 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<IrModuleFragment>, context: JsIrBackend
return rootDeclarations
}
private fun removeUselessDeclarations(modules: Iterable<IrModuleFragment>, usefulDeclarations: Set<IrDeclaration>) {
private fun DceRuntimeDiagnostic.unreachableDeclarationMethod(context: JsIrBackendContext) =
when (this) {
DceRuntimeDiagnostic.LOG -> context.intrinsics.jsUnreachableDeclarationLog
DceRuntimeDiagnostic.EXCEPTION -> context.intrinsics.jsUnreachableDeclarationException
}
private fun processUselessDeclarations(
modules: Iterable<IrModuleFragment>,
usefulDeclarations: Set<IrDeclaration>,
context: JsIrBackendContext
) {
modules.forEach { module ->
module.files.forEach {
it.acceptVoid(object : IrElementVisitorVoid {
@@ -99,7 +122,7 @@ private fun removeUselessDeclarations(modules: Iterable<IrModuleFragment>, 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<IrModuleFragment>, usefu
}
}
private fun IrDeclaration.processUselessDeclaration(context: JsIrBackendContext): List<IrDeclaration>? {
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<IrDeclaration>, context: JsIrBackendContext): Set<IrDeclaration> {
val printReachabilityInfo =
@@ -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
@@ -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 {
@@ -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
)
@@ -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,
@@ -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
}
}
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
)
}
}
}
+2
View File
@@ -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=<name> Specify a compilation module name for IR backend
-Xir-only Disables pre-IR backend
-Xir-per-module Splits generated .js per-module
@@ -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
}
}
@@ -71,7 +71,6 @@ val jsMainSources by task<Sync> {
"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",
@@ -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
@@ -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")
}