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 c807e232336..b4a26fbf8f1 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 @@ -328,6 +328,7 @@ class K2JsIrCompiler : CLICompiler() { exportedDeclarations = setOf(FqName("main")), emitNameSection = arguments.wasmDebug, propertyLazyInitialization = arguments.irPropertyLazyInitialization, + dceEnabled = arguments.irDce, ) val outputWasmFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "wasm")!! outputWasmFile.writeBytes(res.wasm) 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 deleted file mode 100644 index 4c700b24b72..00000000000 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt +++ /dev/null @@ -1,565 +0,0 @@ -/* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.ir.backend.js - -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.* -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.types.getClass -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.JSConfigurationKeys -import org.jetbrains.kotlin.js.config.RuntimeDiagnostic -import org.jetbrains.kotlin.utils.addIfNotNull -import java.util.* - -fun eliminateDeadDeclarations( - modules: Iterable, - context: JsIrBackendContext, - removeUnusedAssociatedObjects: Boolean = true, -) { - - val allRoots = buildRoots(modules, context) - - val usefulDeclarations = usefulDeclarations(allRoots, context, removeUnusedAssociatedObjects) - - processUselessDeclarations(modules, usefulDeclarations, context, removeUnusedAssociatedObjects) -} - -private fun IrField.isConstant(): Boolean { - return correspondingPropertySymbol?.owner?.isConst ?: false -} - -private fun IrDeclaration.addRootsTo(to: MutableCollection, context: JsIrBackendContext) { - when { - this is IrProperty -> { - backingField?.addRootsTo(to, context) - getter?.addRootsTo(to, context) - setter?.addRootsTo(to, context) - } - isEffectivelyExternal() -> { - to += this - } - isExported(context) -> { - to += this - } - this is IrField -> { - // TODO: simplify - if ((initializer != null && !isKotlinPackage() || correspondingPropertySymbol?.owner?.isExported(context) == true) && !isConstant()) { - to += this - } - } - this is IrSimpleFunction -> { - if (correspondingPropertySymbol?.owner?.isExported(context) == true) { - to += this - } - } - } -} - -private fun buildRoots(modules: Iterable, context: JsIrBackendContext): Iterable { - val rootDeclarations = mutableListOf() - val allFiles = (modules.flatMap { it.files } + context.packageLevelJsModules + context.externalPackageFragment.values) - allFiles.forEach { - it.declarations.forEach { - it.addRootsTo(rootDeclarations, context) - } - } - - rootDeclarations += context.testFunsPerFile.values - - val dceRuntimeDiagnostic = context.dceRuntimeDiagnostic - if (dceRuntimeDiagnostic != null) { - rootDeclarations += dceRuntimeDiagnostic.unreachableDeclarationMethod(context).owner - } - - // TODO: Generate calls to main as IR->IR lowering and reference coroutineEmptyContinuation directly - JsMainFunctionDetector(context).getMainFunctionOrNull(modules.last())?.let { mainFunction -> - rootDeclarations += mainFunction - if (mainFunction.isLoweredSuspendFunction(context)) { - rootDeclarations += context.coroutineEmptyContinuation.owner - } - } - - return rootDeclarations -} - - -private fun processUselessDeclarations( - modules: Iterable, - usefulDeclarations: Set, - context: JsIrBackendContext, - removeUnusedAssociatedObjects: Boolean, -) { - modules.forEach { module -> - module.files.forEach { - it.acceptVoid(object : IrElementVisitorVoid { - override fun visitElement(element: IrElement) { - element.acceptChildrenVoid(this) - } - - override fun visitFile(declaration: IrFile) { - process(declaration) - } - - private fun IrConstructorCall.shouldKeepAnnotation(): Boolean { - associatedObject()?.let { obj -> - if (obj !in usefulDeclarations) return false - } - return true - } - - override fun visitClass(declaration: IrClass) { - process(declaration) - // Remove annotations for `findAssociatedObject` feature, which reference objects eliminated by the DCE. - // Otherwise `JsClassGenerator.generateAssociatedKeyProperties` will try to reference the object factory (which is removed). - // That will result in an error from the Namer. It cannot generate a name for an absent declaration. - if (removeUnusedAssociatedObjects && declaration.annotations.any { !it.shouldKeepAnnotation() }) { - declaration.annotations = declaration.annotations.filter { it.shouldKeepAnnotation() } - } - } - - // TODO bring back the primary constructor fix - - private fun process(container: IrDeclarationContainer) { - container.declarations.transformFlat { member -> - if (member !in usefulDeclarations) { - member.processUselessDeclaration(context) - } else { - member.acceptVoid(this) - null - } - } - } - }) - } - } -} - -private fun IrDeclaration.processUselessDeclaration(context: JsIrBackendContext): List? { - return when { - context.dceRuntimeDiagnostic != null -> { - processWithDiagnostic(context) - return null - } - else -> emptyList() - } -} - -private fun RuntimeDiagnostic.unreachableDeclarationMethod(context: JsIrBackendContext) = - when (this) { - RuntimeDiagnostic.LOG -> context.intrinsics.jsUnreachableDeclarationLog - RuntimeDiagnostic.EXCEPTION -> context.intrinsics.jsUnreachableDeclarationException - } - -private fun RuntimeDiagnostic.removingBody(): Boolean { - return this != RuntimeDiagnostic.LOG -} - -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, - removeUnusedAssociatedObjects: Boolean, -): Set { - val printReachabilityInfo = - context.configuration.getBoolean(JSConfigurationKeys.PRINT_REACHABILITY_INFO) || - java.lang.Boolean.getBoolean("kotlin.js.ir.dce.print.reachability.info") - val reachabilityInfo: MutableSet = if (printReachabilityInfo) linkedSetOf() else Collections.emptySet() - - val queue = ArrayDeque() - val result = hashSetOf() - - // This collection contains declarations whose reachability should be propagated to overrides. - // Overriding uncontagious declaration will not lead to becoming a declaration reachable. - // By default, all declarations treated as contagious, it's not the most efficient, but it's safest. - // In case when we access a declaration through a fake-override declaration, the original (real) one will not be marked as contagious, - // so, later, other overrides will not be processed unconditionally only because it overrides a reachable declaration. - // - // The collection must be a subset of [result] set. - val contagiousReachableDeclarations = hashSetOf>() - val constructedClasses = hashSetOf() - - val classesWithObjectAssociations = hashSetOf() - val referencedJsClasses = hashSetOf() - val referencedJsClassesFromExpressions = hashSetOf() - - fun IrDeclaration.enqueue( - from: IrDeclaration?, - description: String?, - isContagious: Boolean = true, - altFromFqn: String? = null - ) { - // Ignore non-external IrProperty because we don't want to generate code for them and codegen doesn't support it. - if (this is IrProperty && !this.isExternal) return - - // TODO check that this is overridable - // it requires fixing how functions with default arguments is handled - val isContagiousOverridableDeclaration = isContagious && this is IrOverridableDeclaration<*> && this.isMemberOfOpenClass - - if (printReachabilityInfo) { - val fromFqn = (from as? IrDeclarationWithName)?.fqNameWhenAvailable?.asString() ?: altFromFqn ?: "" - val toFqn = (this as? IrDeclarationWithName)?.fqNameWhenAvailable?.asString() ?: "" - - val comment = (description ?: "") + (if (isContagiousOverridableDeclaration) "[CONTAGIOUS!]" else "") - val v = "\"$fromFqn\" -> \"$toFqn\"" + (if (comment.isBlank()) "" else " // $comment") - - reachabilityInfo.add(v) - } - - if (isContagiousOverridableDeclaration) { - contagiousReachableDeclarations.add(this as IrOverridableDeclaration<*>) - } - - if (this !in result) { - result.add(this) - queue.addLast(this) - } - } - - // use withInitialIr to avoid ConcurrentModificationException in dce-driven lowering when adding roots' nested declarations (members) - // Add roots - roots.forEach { - it.enqueue(null, null, altFromFqn = "") - } - - // Add roots' nested declarations - roots.forEach { - it.acceptVoid( - object : IrElementVisitorVoid { - override fun visitElement(element: IrElement) { - element.acceptChildrenVoid(this) - } - - override fun visitBody(body: IrBody) { - // Skip - } - - override fun visitDeclaration(declaration: IrDeclarationBase) { - if (declaration !== it) declaration.enqueue(it, "roots' nested declaration") - - super.visitDeclaration(declaration) - } - } - ) - } - - val toStringMethod = - context.irBuiltIns.anyClass.owner.declarations.filterIsInstance().single { it.name.asString() == "toString" } - val equalsMethod = - context.irBuiltIns.anyClass.owner.declarations.filterIsInstance().single { it.name.asString() == "equals" } - val hashCodeMethod = - context.irBuiltIns.anyClass.owner.declarations.filterIsInstance().single { it.name.asString() == "hashCode" } - - while (queue.isNotEmpty()) { - while (queue.isNotEmpty()) { - val declaration = queue.pollFirst() - - fun IrDeclaration.enqueue(description: String, isContagious: Boolean = true) { - enqueue(declaration, description, isContagious) - } - - if (declaration is IrClass) { - declaration.superTypes.forEach { - (it.classifierOrNull as? IrClassSymbol)?.owner?.enqueue("superTypes") - } - - if (declaration.isObject && declaration.isExported(context)) { - context.mapping.objectToGetInstanceFunction[declaration]!! - .enqueue(declaration, "Exported object getInstance function") - } - - declaration.annotations.forEach { - val annotationClass = it.symbol.owner.constructedClass - if (annotationClass.isAssociatedObjectAnnotatedAnnotation) { - classesWithObjectAssociations += declaration - annotationClass.enqueue("@AssociatedObject annotated annotation class") - } - } - } - - if (declaration is IrSimpleFunction && declaration.isFakeOverride) { - declaration.resolveFakeOverride()?.enqueue("real overridden fun", isContagious = false) - } - - // Collect instantiated classes. - if (declaration is IrConstructor) { - declaration.constructedClass.let { - it.enqueue("constructed class") - 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("function access") - } - - override fun visitRawFunctionReference(expression: IrRawFunctionReference) { - super.visitRawFunctionReference(expression) - - expression.symbol.owner.enqueue("raw function access") - } - - override fun visitVariableAccess(expression: IrValueAccessExpression) { - super.visitVariableAccess(expression) - - expression.symbol.owner.enqueue("variable access") - } - - override fun visitFieldAccess(expression: IrFieldAccessExpression) { - super.visitFieldAccess(expression) - - expression.symbol.owner.enqueue("field access") - } - - override fun visitCall(expression: IrCall) { - super.visitCall(expression) - - when (expression.symbol) { - context.intrinsics.jsBoxIntrinsic -> { - val inlineClass = context.inlineClassesUtils.getInlinedClass(expression.getTypeArgument(0)!!)!! - val constructor = inlineClass.declarations.filterIsInstance().single { it.isPrimary } - constructor.enqueue("intrinsic: jsBoxIntrinsic") - } - context.intrinsics.jsClass -> { - val ref = expression.getTypeArgument(0)!!.classifierOrFail.owner as IrDeclaration - ref.enqueue("intrinsic: jsClass") - referencedJsClasses += ref - // When class reference provided as parameter to external function - // It can be instantiated by external JS script - // Need to leave constructor for this - // https://youtrack.jetbrains.com/issue/KT-46672 - // TODO: Possibly solution with origin is not so good - // There is option with applying this hack to jsGetKClass - if (expression.origin == JsStatementOrigins.CLASS_REFERENCE) { - // Maybe we need to filter primary constructor - // Although at this time, we should have only primary constructor - (ref as IrClass) - .constructors - .forEach { - it.enqueue("intrinsic: jsClass (constructor)") - } - } - } - context.reflectionSymbols.getKClassFromExpression -> { - val ref = expression.getTypeArgument(0)?.classOrNull ?: context.irBuiltIns.anyClass - referencedJsClassesFromExpressions += ref.owner - } - context.intrinsics.jsObjectCreate -> { - val classToCreate = expression.getTypeArgument(0)!!.classifierOrFail.owner as IrClass - classToCreate.enqueue("intrinsic: jsObjectCreate") - constructedClasses += classToCreate - } - context.intrinsics.jsEquals -> { - equalsMethod.enqueue("intrinsic: jsEquals") - } - context.intrinsics.jsToString -> { - toStringMethod.enqueue("intrinsic: jsToString") - } - context.intrinsics.jsHashCode -> { - hashCodeMethod.enqueue("intrinsic: jsHashCode") - } - context.intrinsics.jsPlus -> { - if (expression.getValueArgument(0)?.type?.classOrNull == context.irBuiltIns.stringClass) { - toStringMethod.enqueue("intrinsic: jsPlus") - } - } - context.intrinsics.jsConstruct -> { - val callType = expression.getTypeArgument(0)!! - val constructor = callType.getClass()!!.primaryConstructor - constructor!!.enqueue("ctor call from jsConstruct-intrinsic") - } - context.intrinsics.es6DefaultType -> { - //same as jsClass - val ref = expression.getTypeArgument(0)!!.classifierOrFail.owner as IrDeclaration - ref.enqueue("intrinsic: jsClass") - referencedJsClasses += ref - - //Generate klass in `val currResultType = resultType || klass` - val arg = expression.getTypeArgument(0)!! - val klass = arg.getClass() - constructedClasses.addIfNotNull(klass) - } - context.intrinsics.jsInvokeSuspendSuperType, - context.intrinsics.jsInvokeSuspendSuperTypeWithReceiver, - context.intrinsics.jsInvokeSuspendSuperTypeWithReceiverAndParam -> { - invokeFunForLambda(expression) - .enqueue("intrinsic: suspendSuperType") - } - } - } - - override fun visitStringConcatenation(expression: IrStringConcatenation) { - super.visitStringConcatenation(expression) - - toStringMethod.enqueue("string concatenation") - } - }) - } - - fun IrOverridableDeclaration<*>.findOverriddenContagiousDeclaration(): IrOverridableDeclaration<*>? { - for (overriddenSymbol in this.overriddenSymbols) { - val overriddenDeclaration = overriddenSymbol.owner as? IrOverridableDeclaration<*> ?: continue - - if (overriddenDeclaration in contagiousReachableDeclarations) return overriddenDeclaration - - overriddenDeclaration.findOverriddenContagiousDeclaration()?.let { - return it - } - } - - return null - } - - // Handle objects, constructed via `findAssociatedObject` annotation - referencedJsClassesFromExpressions += constructedClasses.filterDescendantsOf(referencedJsClassesFromExpressions) // Grow the set of possible results of instance::class expression - for (klass in classesWithObjectAssociations) { - if (removeUnusedAssociatedObjects && klass !in referencedJsClasses && klass !in referencedJsClassesFromExpressions) continue - - for (annotation in klass.annotations) { - val annotationClass = annotation.symbol.owner.constructedClass - if (removeUnusedAssociatedObjects && annotationClass !in referencedJsClasses) continue - - annotation.associatedObject()?.let { obj -> - context.mapping.objectToGetInstanceFunction[obj]?.enqueue(klass, "associated object factory") - } - } - } - - for (klass in constructedClasses) { - // TODO a better way to support inverse overrides. - for (declaration in ArrayList(klass.declarations)) { - if (declaration in result) continue - - if (declaration is IrOverridableDeclaration<*>) { - declaration.findOverriddenContagiousDeclaration()?.let { - declaration.enqueue(it, "overrides useful declaration") - } - } - - if (declaration is IrSimpleFunction && declaration.getJsNameOrKotlinName().asString() == "valueOf") { - declaration.enqueue(klass, "valueOf") - } - - // 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(klass, "annotated by @JsName") - } - - // A hack to enforce property lowering. - // Until a getter is accessed it doesn't get moved to the declaration list. - if (declaration is IrProperty) { - fun IrSimpleFunction.enqueue(description: String) { - findOverriddenContagiousDeclaration()?.let { enqueue(it, description) } - } - - declaration.getter?.enqueue("(getter) overrides useful declaration") - declaration.setter?.enqueue("(setter) overrides useful declaration") - } - } - } - - context.additionalExportedDeclarations - .forEach { it.enqueue(null, "from additionalExportedDeclarations", altFromFqn = "") } - } - - if (printReachabilityInfo) { - reachabilityInfo.forEach(::println) - } - - return result -} - -private fun Collection.filterDescendantsOf(bases: Collection): Collection { - val visited = hashSetOf() - val baseDescendants = hashSetOf() - baseDescendants += bases - - fun overridesAnyBase(klass: IrClass): Boolean { - if (klass in baseDescendants) return true - if (klass in visited) return false - - visited += klass - - klass.superTypes.forEach { - (it.classifierOrNull as? IrClassSymbol)?.owner?.let { - if (overridesAnyBase(it)) { - baseDescendants += klass - return true - } - } - } - - return false - } - - return this.filter { overridesAnyBase(it) } -} 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 new file mode 100644 index 00000000000..c5272d89319 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/Dce.kt @@ -0,0 +1,119 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.ir.backend.js.dce + +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext +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.expressions.IrBody +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.JSConfigurationKeys +import org.jetbrains.kotlin.js.config.RuntimeDiagnostic + +fun eliminateDeadDeclarations( + modules: Iterable, + context: JsIrBackendContext, + removeUnusedAssociatedObjects: Boolean = true, +) { + val allRoots = buildRoots(modules, context) + + val printReachabilityInfo = + context.configuration.getBoolean(JSConfigurationKeys.PRINT_REACHABILITY_INFO) || + java.lang.Boolean.getBoolean("kotlin.js.ir.dce.print.reachability.info") + + val usefulDeclarations = JsUsefulDeclarationProcessor(context, printReachabilityInfo, removeUnusedAssociatedObjects) + .collectDeclarations(allRoots) + + val uselessDeclarationsProcessor = + UselessDeclarationsRemover(removeUnusedAssociatedObjects, usefulDeclarations, context, context.dceRuntimeDiagnostic) + modules.forEach { module -> + module.files.forEach { + it.acceptVoid(uselessDeclarationsProcessor) + } + } +} + +private fun IrField.isConstant(): Boolean = + correspondingPropertySymbol?.owner?.isConst ?: false + +private fun IrDeclaration.addRootsTo( + nestedVisitor: IrElementVisitorVoid, + context: JsIrBackendContext +) { + when { + this is IrProperty -> { + backingField?.addRootsTo(nestedVisitor, context) + getter?.addRootsTo(nestedVisitor, context) + setter?.addRootsTo(nestedVisitor, context) + } + isEffectivelyExternal() -> { + 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 -> { + if (correspondingPropertySymbol?.owner?.isExported(context) == true) { + acceptVoid(nestedVisitor) + } + } + } +} + +private fun buildRoots(modules: Iterable, context: JsIrBackendContext): List = buildList { + val declarationsCollector = object : IrElementVisitorVoid { + override fun visitElement(element: IrElement): Unit = element.acceptChildrenVoid(this) + override fun visitBody(body: IrBody): Unit = Unit // Skip + + override fun visitDeclaration(declaration: IrDeclarationBase) { + super.visitDeclaration(declaration) + add(declaration) + } + } + + val allFiles = (modules.flatMap { it.files } + context.packageLevelJsModules + context.externalPackageFragment.values) + allFiles.forEach { file -> + file.declarations.forEach { declaration -> + declaration.addRootsTo(declarationsCollector, context) + } + } + + val dceRuntimeDiagnostic = context.dceRuntimeDiagnostic + if (dceRuntimeDiagnostic != null) { + dceRuntimeDiagnostic.unreachableDeclarationMethod(context).owner.acceptVoid(declarationsCollector) + } + + // TODO: Generate calls to main as IR->IR lowering and reference coroutineEmptyContinuation directly + JsMainFunctionDetector(context).getMainFunctionOrNull(modules.last())?.let { mainFunction -> + add(mainFunction) + if (mainFunction.isLoweredSuspendFunction(context)) { + context.coroutineEmptyContinuation.owner.acceptVoid(declarationsCollector) + } + } + + addAll(context.testFunsPerFile.values) + addAll(context.additionalExportedDeclarations) +} + +internal fun RuntimeDiagnostic.unreachableDeclarationMethod(context: JsIrBackendContext) = + when (this) { + RuntimeDiagnostic.LOG -> context.intrinsics.jsUnreachableDeclarationLog + RuntimeDiagnostic.EXCEPTION -> context.intrinsics.jsUnreachableDeclarationException + } + +internal fun IrField.isKotlinPackage() = + fqNameWhenAvailable?.asString()?.startsWith("kotlin") == true \ No newline at end of file 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 new file mode 100644 index 00000000000..8a0ff734baf --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt @@ -0,0 +1,181 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.ir.backend.js.dce + +import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext +import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins +import org.jetbrains.kotlin.ir.backend.js.export.isExported +import org.jetbrains.kotlin.ir.backend.js.utils.associatedObject +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.invokeFunForLambda +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.IrCall +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.types.getClass +import org.jetbrains.kotlin.ir.util.constructedClass +import org.jetbrains.kotlin.ir.util.constructors +import org.jetbrains.kotlin.ir.util.primaryConstructor + +internal class JsUsefulDeclarationProcessor( + override val context: JsIrBackendContext, + printReachabilityInfo: Boolean, + removeUnusedAssociatedObjects: Boolean +) : UsefulDeclarationProcessor(printReachabilityInfo, removeUnusedAssociatedObjects) { + + private val equalsMethod = getMethodOfAny("equals") + private val hashCodeMethod = getMethodOfAny("hashCode") + + override val bodyVisitor: BodyVisitorBase = object : BodyVisitorBase() { + override fun visitCall(expression: IrCall, data: IrDeclaration) { + super.visitCall(expression, data) + when (expression.symbol) { + context.intrinsics.jsBoxIntrinsic -> { + val inlineClass = context.inlineClassesUtils.getInlinedClass(expression.getTypeArgument(0)!!)!! + 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") + referencedJsClasses += ref + // When class reference provided as parameter to external function + // It can be instantiated by external JS script + // Need to leave constructor for this + // https://youtrack.jetbrains.com/issue/KT-46672 + // TODO: Possibly solution with origin is not so good + // There is option with applying this hack to jsGetKClass + if (expression.origin == JsStatementOrigins.CLASS_REFERENCE) { + // Maybe we need to filter primary constructor + // Although at this time, we should have only primary constructor + (ref as IrClass) + .constructors + .forEach { + it.enqueue(data, "intrinsic: jsClass (constructor)") + } + } + } + context.reflectionSymbols.getKClassFromExpression -> { + val ref = expression.getTypeArgument(0)?.classOrNull ?: context.irBuiltIns.anyClass + referencedJsClassesFromExpressions += ref.owner + } + context.intrinsics.jsObjectCreate -> { + val classToCreate = expression.getTypeArgument(0)!!.classifierOrFail.owner as IrClass + classToCreate.enqueue(data, "intrinsic: jsObjectCreate") + 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 + ref.enqueue(data, "intrinsic: jsClass") + referencedJsClasses += ref + + //Generate klass in `val currResultType = resultType || klass` + val arg = expression.getTypeArgument(0)!! + val klass = arg.getClass() + if (klass != null) { + constructedClasses.add(klass) + } + } + context.intrinsics.jsInvokeSuspendSuperType, + context.intrinsics.jsInvokeSuspendSuperTypeWithReceiver, + context.intrinsics.jsInvokeSuspendSuperTypeWithReceiverAndParam -> { + invokeFunForLambda(expression) + .enqueue(data, "intrinsic: suspendSuperType") + } + } + } + } + + override fun processConstructedClassDeclaration(declaration: IrDeclaration) { + if (declaration in result) return + + super.processConstructedClassDeclaration(declaration) + + if (declaration is IrSimpleFunction && declaration.getJsNameOrKotlinName().asString() == "valueOf") { + declaration.enqueue(declaration, "valueOf") + } + + // 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(declaration, "annotated by @JsName") + } + } + + private val referencedJsClasses = hashSetOf() + private val referencedJsClassesFromExpressions = hashSetOf() + + override fun handleAssociatedObjects() { + //Handle objects, constructed via `findAssociatedObject` annotation + referencedJsClassesFromExpressions += constructedClasses.filterDescendantsOf(referencedJsClassesFromExpressions) // Grow the set of possible results of instance::class expression + for (klass in classesWithObjectAssociations) { + if (removeUnusedAssociatedObjects && klass !in referencedJsClasses && klass !in referencedJsClassesFromExpressions) continue + + for (annotation in klass.annotations) { + val annotationClass = annotation.symbol.owner.constructedClass + if (removeUnusedAssociatedObjects && annotationClass !in referencedJsClasses) continue + + annotation.associatedObject()?.let { obj -> + context.mapping.objectToGetInstanceFunction[obj]?.enqueue(klass, "associated object factory") + } + } + } + } + + override fun isExported(declaration: IrDeclaration): Boolean = declaration.isExported(context) +} + + +private fun Collection.filterDescendantsOf(bases: Collection): Collection { + val visited = hashSetOf() + val baseDescendants = hashSetOf() + baseDescendants += bases + + fun overridesAnyBase(klass: IrClass): Boolean { + if (klass in baseDescendants) return true + if (klass in visited) return false + + visited += klass + + klass.superTypes.forEach { + (it.classifierOrNull as? IrClassSymbol)?.owner?.let { klass -> + if (overridesAnyBase(klass)) { + baseDescendants += klass + return true + } + } + } + + return false + } + + return this.filter { overridesAnyBase(it) } +} \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/UsefulDeclarationProcessor.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/UsefulDeclarationProcessor.kt new file mode 100644 index 00000000000..ef76155000b --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/UsefulDeclarationProcessor.kt @@ -0,0 +1,235 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.ir.backend.js.dce + +import org.jetbrains.kotlin.backend.common.ir.isMemberOfOpenClass +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext +import org.jetbrains.kotlin.ir.backend.js.utils.* +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.symbols.IrClassSymbol +import org.jetbrains.kotlin.ir.types.classifierOrNull +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.ir.visitors.IrElementVisitor +import java.util.* + +abstract class UsefulDeclarationProcessor( + private val printReachabilityInfo: Boolean, + protected val removeUnusedAssociatedObjects: Boolean +) { + abstract val context: JsCommonBackendContext + + protected fun getMethodOfAny(name: String): IrDeclaration = + context.irBuiltIns.anyClass.owner.declarations.filterIsInstance().single { it.name.asString() == name } + + protected val toStringMethod: IrDeclaration by lazy { getMethodOfAny("toString") } + protected abstract fun isExported(declaration: IrDeclaration): Boolean + protected abstract val bodyVisitor: BodyVisitorBase + + protected abstract inner class BodyVisitorBase : IrElementVisitor { + + override fun visitValueAccess(expression: IrValueAccessExpression, data: IrDeclaration) = visitVariableAccess(expression, data) + override fun visitGetValue(expression: IrGetValue, data: IrDeclaration) = visitVariableAccess(expression, data) + override fun visitSetValue(expression: IrSetValue, data: IrDeclaration) = visitVariableAccess(expression, data) + + private fun visitVariableAccess(expression: IrValueAccessExpression, data: IrDeclaration) { + visitDeclarationReference(expression, data) + expression.symbol.owner.enqueue(data, "variable access") + } + + override fun visitElement(element: IrElement, data: IrDeclaration) { + element.acceptChildren(this, data) + } + + override fun visitFunctionAccess(expression: IrFunctionAccessExpression, data: IrDeclaration) { + super.visitFunctionAccess(expression, data) + expression.symbol.owner.enqueue(data, "function access") + } + + override fun visitRawFunctionReference(expression: IrRawFunctionReference, data: IrDeclaration) { + super.visitRawFunctionReference(expression, data) + expression.symbol.owner.enqueue(data, "raw function access") + } + + override fun visitFieldAccess(expression: IrFieldAccessExpression, data: IrDeclaration) { + super.visitFieldAccess(expression, data) + expression.symbol.owner.enqueue(data, "field access") + } + + override fun visitStringConcatenation(expression: IrStringConcatenation, data: IrDeclaration) { + super.visitStringConcatenation(expression, data) + toStringMethod.enqueue(data, "string concatenation") + } + } + + private fun addReachabilityInfoIfNeeded( + from: IrDeclaration, + to: IrDeclaration, + description: String?, + isContagiousOverridableDeclaration: Boolean, + ) { + if (!printReachabilityInfo) return + val fromFqn = (from as? IrDeclarationWithName)?.fqNameWhenAvailable?.asString() ?: "" + val toFqn = (to as? IrDeclarationWithName)?.fqNameWhenAvailable?.asString() ?: "" + val comment = (description ?: "") + (if (isContagiousOverridableDeclaration) "[CONTAGIOUS!]" else "") + val info = "\"$fromFqn\" -> \"$toFqn\"" + (if (comment.isBlank()) "" else " // $comment") + reachabilityInfo.add(info) + } + + protected fun IrDeclaration.enqueue( + from: IrDeclaration, + description: String?, + isContagious: Boolean = true, + ) { + // Ignore non-external IrProperty because we don't want to generate code for them and codegen doesn't support it. + if (this is IrProperty && !this.isExternal) return + + // TODO check that this is overridable + // it requires fixing how functions with default arguments is handled + val isContagiousOverridableDeclaration = isContagious && this is IrOverridableDeclaration<*> && this.isMemberOfOpenClass + + addReachabilityInfoIfNeeded(from, this, description, isContagiousOverridableDeclaration) + + if (isContagiousOverridableDeclaration) { + contagiousReachableDeclarations.add(this as IrOverridableDeclaration<*>) + } + + if (this !in result) { + result.add(this) + queue.addLast(this) + } + } + + // This collection contains declarations whose reachability should be propagated to overrides. + // Overriding uncontagious declaration will not lead to becoming a declaration reachable. + // By default, all declarations treated as contagious, it's not the most efficient, but it's safest. + // In case when we access a declaration through a fake-override declaration, the original (real) one will not be marked as contagious, + // so, later, other overrides will not be processed unconditionally only because it overrides a reachable declaration. + // + // The collection must be a subset of [result] set. + private val contagiousReachableDeclarations = hashSetOf>() + protected val constructedClasses = hashSetOf() + private val reachabilityInfo: MutableSet = if (printReachabilityInfo) linkedSetOf() else Collections.emptySet() + private val queue = ArrayDeque() + protected val result = hashSetOf() + protected val classesWithObjectAssociations = hashSetOf() + + protected open fun processField(irField: IrField): Unit = Unit + + protected open fun processClass(irClass: IrClass) { + irClass.superTypes.forEach { + (it.classifierOrNull as? IrClassSymbol)?.owner?.enqueue(irClass, "superTypes") + } + + if (irClass.isObject && isExported(irClass)) { + context.mapping.objectToGetInstanceFunction[irClass] + ?.enqueue(irClass, "Exported object getInstance function") + } + + irClass.annotations.forEach { + val annotationClass = it.symbol.owner.constructedClass + if (annotationClass.isAssociatedObjectAnnotatedAnnotation) { + classesWithObjectAssociations += irClass + annotationClass.enqueue(irClass, "@AssociatedObject annotated annotation class") + } + } + } + + protected open fun processSimpleFunction(irFunction: IrSimpleFunction) { + if (irFunction.isFakeOverride) { + irFunction.resolveFakeOverride()?.enqueue(irFunction, "real overridden fun", isContagious = false) + } + } + + protected open fun processConstructor(irConstructor: IrConstructor) { + // Collect instantiated classes. + irConstructor.constructedClass.let { + it.enqueue(irConstructor, "constructed class") + constructedClasses += it + } + } + + protected open fun processConstructedClassDeclaration(declaration: IrDeclaration) { + if (declaration in result) return + + fun IrOverridableDeclaration<*>.findOverriddenContagiousDeclaration(): IrOverridableDeclaration<*>? { + for (overriddenSymbol in this.overriddenSymbols) { + val overriddenDeclaration = overriddenSymbol.owner as? IrOverridableDeclaration<*> ?: continue + + if (overriddenDeclaration in contagiousReachableDeclarations) return overriddenDeclaration + + overriddenDeclaration.findOverriddenContagiousDeclaration()?.let { + return it + } + } + return null + } + + if (declaration is IrOverridableDeclaration<*>) { + declaration.findOverriddenContagiousDeclaration()?.let { + declaration.enqueue(it, "overrides useful declaration") + } + } + + // A hack to enforce property lowering. + // Until a getter is accessed it doesn't get moved to the declaration list. + if (declaration is IrProperty) { + declaration.getter?.run { + findOverriddenContagiousDeclaration()?.let { enqueue(declaration, "(getter) overrides useful declaration") } + } + declaration.setter?.run { + findOverriddenContagiousDeclaration()?.let { enqueue(declaration, "(setter) overrides useful declaration") } + } + } + } + + protected open fun handleAssociatedObjects(): Unit = Unit + + fun collectDeclarations(rootDeclarations: Iterable): Set { + + rootDeclarations.forEach { + it.enqueue(it, "") + } + + while (queue.isNotEmpty()) { + while (queue.isNotEmpty()) { + val declaration = queue.pollFirst() + + when (declaration) { + is IrClass -> processClass(declaration) + is IrSimpleFunction -> processSimpleFunction(declaration) + is IrConstructor -> processConstructor(declaration) + is IrField -> processField(declaration) + } + + val body = when (declaration) { + is IrFunction -> declaration.body + is IrField -> declaration.initializer + is IrVariable -> declaration.initializer + else -> null + } + + body?.accept(bodyVisitor, declaration) + } + + handleAssociatedObjects() + + for (klass in constructedClasses) { + // TODO a better way to support inverse overrides. + for (declaration in klass.declarations) { + processConstructedClassDeclaration(declaration) + } + } + } + + if (printReachabilityInfo) { + reachabilityInfo.forEach(::println) + } + + return result + } +} \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/UselessDeclarationsRemover.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/UselessDeclarationsRemover.kt new file mode 100644 index 00000000000..0dc878f4d44 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/UselessDeclarationsRemover.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.ir.backend.js.dce + +import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET +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.* +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid +import org.jetbrains.kotlin.ir.visitors.acceptVoid +import org.jetbrains.kotlin.js.config.RuntimeDiagnostic + +class UselessDeclarationsRemover( + private val removeUnusedAssociatedObjects: Boolean, + private val usefulDeclarations: Set, + private val context: JsIrBackendContext, + private val dceRuntimeDiagnostic: RuntimeDiagnostic?, +) : IrElementVisitorVoid { + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + } + + override fun visitFile(declaration: IrFile) { + process(declaration) + } + + private fun IrConstructorCall.shouldKeepAnnotation(): Boolean { + associatedObject()?.let { obj -> + if (obj !in usefulDeclarations) return false + } + return true + } + + override fun visitClass(declaration: IrClass) { + process(declaration) + // Remove annotations for `findAssociatedObject` feature, which reference objects eliminated by the DCE. + // Otherwise `JsClassGenerator.generateAssociatedKeyProperties` will try to reference the object factory (which is removed). + // That will result in an error from the Namer. It cannot generate a name for an absent declaration. + if (removeUnusedAssociatedObjects && declaration.annotations.any { !it.shouldKeepAnnotation() }) { + declaration.annotations = declaration.annotations.filter { it.shouldKeepAnnotation() } + } + } + + // TODO bring back the primary constructor fix + private fun process(container: IrDeclarationContainer) { + container.declarations.transformFlat { member -> + if (member !in usefulDeclarations) { + member.processUselessDeclaration() + } else { + member.acceptVoid(this) + null + } + } + } + + private fun IrDeclaration.processUselessDeclaration(): List? { + return when { + dceRuntimeDiagnostic != null -> { + processWithDiagnostic(dceRuntimeDiagnostic) + null + } + else -> emptyList() + } + } + + private fun RuntimeDiagnostic.removingBody(): Boolean = + this != RuntimeDiagnostic.LOG + + private fun IrDeclaration.processWithDiagnostic(dceRuntimeDiagnostic: RuntimeDiagnostic) { + when (this) { + is IrFunction -> processFunctionWithDiagnostic(dceRuntimeDiagnostic) + is IrField -> processFieldWithDiagnostic() + is IrDeclarationContainer -> declarations.forEach { it.processWithDiagnostic(dceRuntimeDiagnostic) } + } + } + + private fun IrFunction.processFunctionWithDiagnostic(dceRuntimeDiagnostic: RuntimeDiagnostic) { + 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 + } + } +} \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt index 3589b56b6c9..ffbd623e14d 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt @@ -8,7 +8,7 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs import org.jetbrains.kotlin.ir.backend.js.CompilationOutputs 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.dce.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 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 c6e64c7c984..eccbd26de31 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 @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.ir.backend.js.* +import org.jetbrains.kotlin.ir.backend.js.dce.eliminateDeadDeclarations import org.jetbrains.kotlin.ir.backend.js.export.* import org.jetbrains.kotlin.ir.backend.js.lower.StaticMembersLowering import org.jetbrains.kotlin.ir.backend.js.utils.* diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt index 18891a86e2a..b2ca0d4ff83 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.backend.wasm import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel +import org.jetbrains.kotlin.backend.wasm.dce.eliminateDeadDeclarations import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmCompiledModuleFragment import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmModuleFragmentGenerator import org.jetbrains.kotlin.backend.wasm.lower.markExportedDeclarations @@ -31,6 +32,7 @@ fun compileWasm( exportedDeclarations: Set = emptySet(), propertyLazyInitialization: Boolean, emitNameSection: Boolean = false, + dceEnabled: Boolean = false, ): WasmCompilerResult { val mainModule = depsDescriptors.mainModule val configuration = depsDescriptors.compilerConfiguration @@ -69,8 +71,12 @@ fun compileWasm( wasmPhases.invokeToplevel(phaseConfig, context, moduleFragment) + if (dceEnabled) { + eliminateDeadDeclarations(listOf(moduleFragment), context) + } + val compiledWasmModule = WasmCompiledModuleFragment(context.irBuiltIns) - val codeGenerator = WasmModuleFragmentGenerator(context, compiledWasmModule) + val codeGenerator = WasmModuleFragmentGenerator(context, compiledWasmModule, allowIncompleteImplementations = dceEnabled) codeGenerator.generateModule(moduleFragment) val linkedModule = compiledWasmModule.linkWasmCompiledFragments() diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/Dce.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/Dce.kt new file mode 100644 index 00000000000..4ff419388e6 --- /dev/null +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/Dce.kt @@ -0,0 +1,64 @@ +/* + * 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.backend.wasm.dce + +import org.jetbrains.kotlin.backend.wasm.WasmBackendContext +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.backend.js.utils.* +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.IrBody +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.JSConfigurationKeys + +internal fun eliminateDeadDeclarations(modules: List, context: WasmBackendContext) { + val printReachabilityInfo = + context.configuration.getBoolean(JSConfigurationKeys.PRINT_REACHABILITY_INFO) || + java.lang.Boolean.getBoolean("kotlin.wasm.dce.print.reachability.info") + + val usefulDeclarations = WasmUsefulDeclarationProcessor( + context = context, + printReachabilityInfo = printReachabilityInfo + ).collectDeclarations(rootDeclarations = buildRoots(modules, context)) + + val remover = WasmUselessDeclarationsRemover(usefulDeclarations) + modules.onAllFiles { + acceptVoid(remover) + } +} + +private fun buildRoots(modules: List, context: WasmBackendContext): List = buildList { + val declarationsCollector = object : IrElementVisitorVoid { + override fun visitElement(element: IrElement): Unit = element.acceptChildrenVoid(this) + override fun visitBody(body: IrBody): Unit = Unit // Skip + + override fun visitDeclaration(declaration: IrDeclarationBase) { + super.visitDeclaration(declaration) + add(declaration) + } + } + + modules.onAllFiles { + declarations.forEach { declaration -> + if (declaration.isJsExport()) { + declaration.acceptVoid(declarationsCollector) + } + } + } + + add(context.irBuiltIns.throwableClass.owner) + add(context.mainCallsWrapperFunction) + add(context.fieldInitFunction) +} + +private inline fun List.onAllFiles(body: IrFile.() -> Unit) { + forEach { module -> + module.files.forEach { file -> + file.body() + } + } +} \ No newline at end of file diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/WasmUsefulDeclarationProcessor.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/WasmUsefulDeclarationProcessor.kt new file mode 100644 index 00000000000..01dd4071a83 --- /dev/null +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/WasmUsefulDeclarationProcessor.kt @@ -0,0 +1,205 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.backend.wasm.dce + +import org.jetbrains.kotlin.backend.common.ir.isOverridable +import org.jetbrains.kotlin.backend.wasm.WasmBackendContext +import org.jetbrains.kotlin.backend.wasm.ir2wasm.* +import org.jetbrains.kotlin.backend.wasm.utils.* +import org.jetbrains.kotlin.ir.backend.js.dce.UsefulDeclarationProcessor +import org.jetbrains.kotlin.ir.backend.js.utils.* +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import org.jetbrains.kotlin.wasm.ir.* + +internal class WasmUsefulDeclarationProcessor( + override val context: WasmBackendContext, + printReachabilityInfo: Boolean +) : UsefulDeclarationProcessor(printReachabilityInfo, removeUnusedAssociatedObjects = false) { + + private val unitGetInstance: IrSimpleFunction = context.findUnitGetInstanceFunction() + + override val bodyVisitor: BodyVisitorBase = object : BodyVisitorBase() { + override fun visitConst(expression: IrConst<*>, data: IrDeclaration) = when (expression.kind) { + is IrConstKind.Null -> expression.type.enqueueType(data, "expression type") + is IrConstKind.String -> context.wasmSymbols.stringGetLiteral.owner + .enqueue(data, "String literal intrinsic getter stringGetLiteral") + else -> Unit + } + + private fun tryToProcessIntrinsicCall(from: IrDeclaration, call: IrCall): Boolean = when (call.symbol) { + context.wasmSymbols.unboxIntrinsic -> { + val fromType = call.getTypeArgument(0) + if (fromType != null && !fromType.isNothing() && !fromType.isNullableNothing()) { + val backingField = call.getTypeArgument(1) + ?.let { context.inlineClassesUtils.getInlinedClass(it) } + ?.let { getInlineClassBackingField(it) } + backingField?.enqueue(from, "backing inline class field for unboxIntrinsic") + } + true + } + context.wasmSymbols.wasmClassId, + context.wasmSymbols.wasmInterfaceId, + context.wasmSymbols.wasmRefCast -> { + call.getTypeArgument(0)?.getClass()?.enqueue(from, "generic intrinsic ${call.symbol.owner.name}") + true + } + else -> false + } + + private fun tryToProcessWasmOpIntrinsicCall(from: IrDeclaration, call: IrCall, function: IrFunction): Boolean { + if (function.hasWasmNoOpCastAnnotation()) { + return true + } + + val opString = function.getWasmOpAnnotation() + if (opString != null) { + val op = WasmOp.valueOf(opString) + when (op.immediates.size) { + 0 -> { + if (op == WasmOp.REF_TEST) { + call.getTypeArgument(0)?.enqueueRuntimeClassOrAny(from, "REF_TEST") + } + } + 1 -> { + if (op.immediates.firstOrNull() == WasmImmediateKind.STRUCT_TYPE_IDX) { + function.dispatchReceiverParameter?.type?.classOrNull?.owner?.enqueue(from, "STRUCT_TYPE_IDX") + } + } + } + return true + } + return false + } + + override fun visitCall(expression: IrCall, data: IrDeclaration) { + super.visitCall(expression, data) + + if (expression.symbol == context.wasmSymbols.boxIntrinsic) { + expression.getTypeArgument(0)?.enqueueRuntimeClassOrAny(data, "boxIntrinsic") + return + } + + val function: IrFunction = expression.symbol.owner.realOverrideTarget + if (function.returnType == context.irBuiltIns.unitType) { + unitGetInstance.enqueue(data, "function Unit return type") + } + + if (tryToProcessIntrinsicCall(data, expression)) return + if (tryToProcessWasmOpIntrinsicCall(data, expression, function)) return + + val isSuperCall = expression.superQualifierSymbol != null + if (function is IrSimpleFunction && function.isOverridable && !isSuperCall) { + val klass = function.parentAsClass + if (!klass.isInterface) { + context.wasmSymbols.getVirtualMethodId.owner.enqueue(data, "call on class receiver") + } else { + klass.enqueue(data, "receiver class") + context.wasmSymbols.getInterfaceImplId.owner.enqueue(data, "call on interface receiver") + } + function.enqueue(data, "method call") + } + } + } + + private fun IrType.getInlinedValueTypeIfAny(): IrType? = when (this) { + context.irBuiltIns.booleanType, + context.irBuiltIns.byteType, + context.irBuiltIns.shortType, + context.irBuiltIns.charType, + context.irBuiltIns.booleanType, + context.irBuiltIns.byteType, + context.irBuiltIns.shortType, + context.irBuiltIns.intType, + context.irBuiltIns.charType, + context.irBuiltIns.longType, + context.irBuiltIns.floatType, + context.irBuiltIns.doubleType, + context.irBuiltIns.nothingType, + context.wasmSymbols.voidType -> null + else -> when { + isBuiltInWasmRefType(this) -> null + erasedUpperBound?.isExternal == true -> null + else -> when (val ic = context.inlineClassesUtils.getInlinedClass(this)) { + null -> this + else -> context.inlineClassesUtils.getInlineClassUnderlyingType(ic).getInlinedValueTypeIfAny() + } + } + } + + private fun IrType.enqueueRuntimeClassOrAny(from: IrDeclaration, info: String): Unit = + (this.getRuntimeClass ?: context.wasmSymbols.any.owner).enqueue(from, info, isContagious = false) + + private fun IrType.enqueueType(from: IrDeclaration, info: String) { + getInlinedValueTypeIfAny() + ?.enqueueRuntimeClassOrAny(from, info) + } + + private fun IrDeclaration.enqueueParentClass() { + parentClassOrNull?.enqueue(this, "parent class", isContagious = false) + } + + override fun processField(irField: IrField) { + super.processField(irField) + irField.enqueueParentClass() + irField.type.enqueueType(irField, "field types") + } + + override fun processClass(irClass: IrClass) { + super.processClass(irClass) + + irClass.getWasmArrayAnnotation()?.type + ?.enqueueType(irClass, "array type for wasm array annotated") + + if (context.inlineClassesUtils.isClassInlineLike(irClass)) { + irClass.declarations + .firstIsInstanceOrNull() + ?.takeIf { it.isPrimary } + ?.enqueue(irClass, "inline class primary ctor") + } + } + + private fun IrValueParameter.enqueueValueParameterType(from: IrDeclaration) { + if (context.inlineClassesUtils.shouldValueParameterBeBoxed(this)) { + type.enqueueRuntimeClassOrAny(from, "function ValueParameterType") + } else { + type.enqueueType(from, "function ValueParameterType") + } + } + + private fun processIrFunction(irFunction: IrFunction) { + if (irFunction.isFakeOverride) return + + val isIntrinsic = irFunction.hasWasmNoOpCastAnnotation() || irFunction.getWasmOpAnnotation() != null + if (isIntrinsic) return + + irFunction.getEffectiveValueParameters().forEach { it.enqueueValueParameterType(irFunction) } + irFunction.returnType.enqueueType(irFunction, "function return type") + } + + override fun processSimpleFunction(irFunction: IrSimpleFunction) { + super.processSimpleFunction(irFunction) + irFunction.enqueueParentClass() + if (irFunction.isFakeOverride) { + irFunction.overriddenSymbols.forEach { overridden -> + overridden.owner.enqueue(irFunction, "original for fake-override") + } + } + processIrFunction(irFunction) + } + + override fun processConstructor(irConstructor: IrConstructor) { + super.processConstructor(irConstructor) + if (!context.inlineClassesUtils.isClassInlineLike(irConstructor.parentAsClass)) { + processIrFunction(irConstructor) + } + } + + override fun isExported(declaration: IrDeclaration): Boolean = declaration.isJsExport() +} \ No newline at end of file diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/WasmUselessDeclarationsRemover.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/WasmUselessDeclarationsRemover.kt new file mode 100644 index 00000000000..3724aa91b95 --- /dev/null +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/dce/WasmUselessDeclarationsRemover.kt @@ -0,0 +1,44 @@ +/* + * 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.backend.wasm.dce + +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrDeclaration +import org.jetbrains.kotlin.ir.declarations.IrDeclarationContainer +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.util.transformFlat +import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid +import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid +import org.jetbrains.kotlin.ir.visitors.acceptVoid + +class WasmUselessDeclarationsRemover( + private val usefulDeclarations: Set +) : IrElementVisitorVoid { + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + } + + override fun visitFile(declaration: IrFile) { + process(declaration) + } + + override fun visitClass(declaration: IrClass) { + process(declaration) + } + + // TODO bring back the primary constructor fix + private fun process(container: IrDeclarationContainer) { + container.declarations.transformFlat { member -> + if (member !in usefulDeclarations) { + emptyList() + } else { + member.acceptVoid(this) + null + } + } + } +} \ No newline at end of file diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt index 24f6a8984fd..b269e75e3a3 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt @@ -271,7 +271,6 @@ class BodyGenerator(val context: WasmFunctionCodegenContext) : IrElementVisitorV } } - if (tryToGenerateIntrinsicCall(call, function)) { if (function.returnType == irBuiltIns.unitType) body.buildGetUnit() @@ -284,7 +283,7 @@ class BodyGenerator(val context: WasmFunctionCodegenContext) : IrElementVisitorV val klass = function.parentAsClass if (!klass.isInterface) { val classMetadata = context.getClassMetadata(klass.symbol) - val vfSlot = classMetadata.virtualMethods.map { it.function }.indexOf(function) + val vfSlot = classMetadata.virtualMethods.indexOfFirst { it.function == function } // Dispatch receiver should be simple and without side effects at this point // TODO: Verify generateExpression(call.dispatchReceiver!!) diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt index e48a9549772..ca40fa669ed 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt @@ -30,7 +30,7 @@ import org.jetbrains.kotlin.ir.visitors.acceptVoid import org.jetbrains.kotlin.name.parentOrNull import org.jetbrains.kotlin.wasm.ir.* -class DeclarationGenerator(val context: WasmModuleCodegenContext) : IrElementVisitorVoid { +class DeclarationGenerator(val context: WasmModuleCodegenContext, private val allowIncompleteImplementations: Boolean) : IrElementVisitorVoid { // Shortcuts private val backendContext: WasmBackendContext = context.backendContext @@ -238,7 +238,6 @@ class DeclarationGenerator(val context: WasmModuleCodegenContext) : IrElementVis wasmExpressionGenerator.buildRttCanon(wasmGcType) } - val rtt = WasmGlobal( name = "rtt_of_$nameStr", isMutable = false, @@ -258,13 +257,16 @@ class DeclarationGenerator(val context: WasmModuleCodegenContext) : IrElementVis // TODO: Cache it val interfaceMetadata = InterfaceMetadata(i, irBuiltIns) val table = interfaceMetadata.methods.associate { method -> - val classMethod: VirtualMethodMetadata = - metadata.virtualMethods - .find { it.signature == method.signature } // TODO: Use map - ?: error("Cannot find class implementation of method ${method.signature} in class ${declaration.fqNameWhenAvailable}") + val classMethod: VirtualMethodMetadata? = metadata.virtualMethods + .find { it.signature == method.signature && it.function.modality != Modality.ABSTRACT } // TODO: Use map - method.function.symbol as IrFunctionSymbol to context.referenceFunction(classMethod.function.symbol) + if (classMethod == null && !allowIncompleteImplementations) { + error("Cannot find class implementation of method ${method.signature} in class ${declaration.fqNameWhenAvailable}") + } + val matchedMethod = classMethod?.let { context.referenceFunction(it.function.symbol) } + method.function.symbol as IrFunctionSymbol to matchedMethod } + context.registerInterfaceImplementationMethod( interfaceImplementation, table diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt index 1c7817e009d..35b3077f4ab 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt @@ -74,7 +74,7 @@ class WasmCompiledModuleFragment(val irBuiltIns: IrBuiltIns) { ReferencableElements() val interfaceImplementationsMethods = - LinkedHashMap>>() + LinkedHashMap?>>() val exports = mutableListOf>() @@ -83,6 +83,7 @@ class WasmCompiledModuleFragment(val irBuiltIns: IrBuiltIns) { val jsFuns = mutableListOf() class FunWithPriority(val function: WasmFunction, val priority: String) + val initFunctions = mutableListOf() val scratchMemAddr = WasmSymbol() @@ -229,28 +230,34 @@ class WasmCompiledModuleFragment(val irBuiltIns: IrBuiltIns) { ) val interfaceTableElementsLists = interfaceMethodTables.defined.keys.associateWith { - mutableMapOf>() + mutableMapOf?>() } - interfaceImplementationIds.forEach { ii: InterfaceImplementation, implId: Int -> - for ((interfaceFunction: IrFunctionSymbol, wasmFunction: WasmSymbol) in interfaceImplementationsMethods[ii]!!) { + for ((ii: InterfaceImplementation, implId: Int) in interfaceImplementationIds) { + for ((interfaceFunction: IrFunctionSymbol, wasmFunction: WasmSymbol?) in interfaceImplementationsMethods[ii]!!) { interfaceTableElementsLists[interfaceFunction]!![implId] = wasmFunction } } val interfaceTableElements = interfaceTableElementsLists.map { (interfaceFunction, methods) -> - val type = interfaceMethodTables.defined[interfaceFunction]!!.elementType + val methodTable = interfaceMethodTables.defined[interfaceFunction]!! + val type = methodTable.elementType val functions = MutableList(methods.size) { idx -> - val wasmFunc = methods[idx]!! + val wasmFunc = methods[idx] val expression = buildWasmExpression { - buildInstr(WasmOp.REF_FUNC, WasmImmediate.FuncIdx(wasmFunc)) + if (wasmFunc != null) { + buildInstr(WasmOp.REF_FUNC, WasmImmediate.FuncIdx(wasmFunc)) + } else { + //DCE could remove implementation from class, so we should to put a stub into method implementations table + buildRefNull(type.getHeapType()) + } } WasmTable.Value.Expression(expression) } WasmElement( type, values = functions, - WasmElement.Mode.Active(interfaceMethodTables.defined[interfaceFunction]!!, offsetExpr) + WasmElement.Mode.Active(methodTable, offsetExpr) ) } diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContext.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContext.kt index f793351dc69..8048b95d733 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContext.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContext.kt @@ -35,7 +35,7 @@ interface WasmModuleCodegenContext : WasmBaseCodegenContext { fun registerInterfaceImplementationMethod( interfaceImplementation: InterfaceImplementation, - table: Map>, + table: Map?>, ) fun referenceInterfaceImplementationId(interfaceImplementation: InterfaceImplementation): WasmSymbol diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContextImpl.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContextImpl.kt index 98a125089ac..2d62e5f306b 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContextImpl.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContextImpl.kt @@ -126,7 +126,7 @@ class WasmModuleCodegenContextImpl( override fun registerInterfaceImplementationMethod( interfaceImplementation: InterfaceImplementation, - table: Map> + table: Map?> ) { wasmFragment.interfaceImplementationsMethods[interfaceImplementation] = table } diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleFragmentGenerator.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleFragmentGenerator.kt index 77a6c8a64c8..507e21fc7fc 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleFragmentGenerator.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleFragmentGenerator.kt @@ -13,14 +13,16 @@ import org.jetbrains.kotlin.ir.visitors.acceptVoid class WasmModuleFragmentGenerator( backendContext: WasmBackendContext, - wasmModuleFragment: WasmCompiledModuleFragment + wasmModuleFragment: WasmCompiledModuleFragment, + allowIncompleteImplementations: Boolean, ) { private val declarationGenerator = DeclarationGenerator( WasmModuleCodegenContextImpl( backendContext, - wasmModuleFragment - ) + wasmModuleFragment, + ), + allowIncompleteImplementations ) fun generateModule(irModuleFragment: IrModuleFragment) { diff --git a/compiler/testData/codegen/box/properties/sideEffectInTopLevelInitializerMultiModule.kt b/compiler/testData/codegen/box/properties/sideEffectInTopLevelInitializerMultiModule.kt index 2bda2ce303d..f71d5a096f7 100644 --- a/compiler/testData/codegen/box/properties/sideEffectInTopLevelInitializerMultiModule.kt +++ b/compiler/testData/codegen/box/properties/sideEffectInTopLevelInitializerMultiModule.kt @@ -2,6 +2,8 @@ // FIR status: not supported in JVM // IGNORE_BACKEND: JVM, JVM_IR // IGNORE_BACKEND: NATIVE +// IGNORE_BACKEND: WASM +// WASM_MUTE_REASON: LAZY_INIT_PROPERTIES // IGNORE_LIGHT_ANALYSIS // MODULE: lib1 // FILE: lib1.kt 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 bd197b229c1..e71e033d735 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 @@ -16,6 +16,7 @@ import org.jetbrains.kotlin.ir.backend.js.codegen.CompilerOutputSink import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationOptions import org.jetbrains.kotlin.ir.backend.js.codegen.generateEsModules +import org.jetbrains.kotlin.ir.backend.js.dce.eliminateDeadDeclarations import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerDesc import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformerTmp diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/testOld/BasicWasmBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/testOld/BasicWasmBoxTest.kt index a16ce9fc023..3951674ecc6 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/testOld/BasicWasmBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/testOld/BasicWasmBoxTest.kt @@ -33,7 +33,6 @@ import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.resolve.CompilerEnvironment import org.jetbrains.kotlin.test.* -import org.jetbrains.kotlin.test.directives.JsEnvironmentConfigurationDirectives import org.jetbrains.kotlin.test.util.KtTestUtil import java.io.Closeable import java.io.File @@ -176,15 +175,14 @@ abstract class BasicWasmBoxTest( AnalyzerWithCompilerReport(config.configuration) ) - val directives = KotlinTestUtils.parseDirectives(FileUtil.loadFile(testFile)) - val compilerResult = compileWasm( sourceModule, phaseConfig = phaseConfig, irFactory = IrFactoryImpl, exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))), - propertyLazyInitialization = directives.contains(JsEnvironmentConfigurationDirectives.PROPERTY_LAZY_INITIALIZATION.name), + propertyLazyInitialization = true, emitNameSection = true, + dceEnabled = true, ) outputWatFile.write(compilerResult.wat) diff --git a/js/js.translator/testData/box/native/kt2209.kt b/js/js.translator/testData/box/native/kt2209.kt index 39759e7c5a9..0e9460c1929 100644 --- a/js/js.translator/testData/box/native/kt2209.kt +++ b/js/js.translator/testData/box/native/kt2209.kt @@ -1,4 +1,6 @@ // EXPECTED_REACHABLE_NODES: 1281 +// IGNORE_BACKEND: WASM +// WASM_MUTE_REASON: LAZY_INIT_PROPERTIES package foo external interface Chrome {