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 754e9120d7d..1cc6e927e07 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 @@ -16,9 +16,13 @@ import org.jetbrains.kotlin.ir.types.classOrNull import org.jetbrains.kotlin.ir.types.classifierOrFail import org.jetbrains.kotlin.ir.types.classifierOrNull import org.jetbrains.kotlin.ir.util.* -import org.jetbrains.kotlin.ir.visitors.* +import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid +import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid +import org.jetbrains.kotlin.ir.visitors.acceptVoid import java.util.* +private val PRINT_REACHABILITY_INFO = java.lang.Boolean.getBoolean("kotlin.js.ir.dce.print.reachability.info") + fun eliminateDeadDeclarations( module: IrModuleFragment, context: JsIrBackendContext, @@ -95,43 +99,64 @@ private fun removeUselessDeclarations(module: IrModuleFragment, usefulDeclaratio } fun usefulDeclarations(roots: Iterable, context: JsIrBackendContext): Set { + val reachabilityInfo: MutableSet = if (PRINT_REACHABILITY_INFO) linkedSetOf() else Collections.emptySet() + val queue = ArrayDeque() val result = hashSetOf() val constructedClasses = hashSetOf() - fun IrDeclaration.enqueue() { - if ((this !is IrProperty || this.isExternal) && this !in result) { - result.add(this) - queue.addLast(this) - } - } - - fun Iterable.withNested(): Iterable { - val result = mutableListOf() - - this.forEach { - it.acceptVoid(object : IrElementVisitorVoid { - override fun visitElement(element: IrElement) { - element.acceptChildrenVoid(this) - } - - override fun visitBody(body: IrBody) { - // Skip - } - - override fun visitDeclaration(declaration: IrDeclaration) { - super.visitDeclaration(declaration) - result += declaration - } - }) + fun IrDeclaration.enqueue( + from: IrDeclaration?, + description: String?, + altFromFqn: String? = null + ) { + if (this is IrProperty && !this.isExternal || + this in result + ) { + return } - return result + if (PRINT_REACHABILITY_INFO) { + val fromFqn = (from as? IrDeclarationWithName)?.fqNameWhenAvailable?.asString() ?: altFromFqn ?: "" + + val toFqn = (this as? IrDeclarationWithName)?.fqNameWhenAvailable?.asString() ?: "" + + val v = "\"$fromFqn\" -> \"$toFqn\"" + (if (description.isNullOrBlank()) "" else " // $description") + + reachabilityInfo.add(v) + } + + result.add(this) + queue.addLast(this) } - // Add roots, including nested declarations + // use withInitialIr to avoid ConcurrentModificationException in dce-driven lowering when adding roots' nested declarations (members) stageController.withInitialIr { - roots.withNested().forEach { it.enqueue() } + // 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: IrDeclaration) { + if (declaration !== it) declaration.enqueue(it, "roots' nested declaration") + + super.visitDeclaration(declaration) + } + } + ) + } } val toStringMethod = @@ -148,29 +173,33 @@ fun usefulDeclarations(roots: Iterable, context: JsIrBackendConte // TODO remove? stageController.lazyLower(declaration) + fun IrDeclaration.enqueue(description: String) { + enqueue(declaration, description) + } + if (declaration is IrClass) { declaration.superTypes.forEach { - (it.classifierOrNull as? IrClassSymbol)?.owner?.enqueue() + (it.classifierOrNull as? IrClassSymbol)?.owner?.enqueue("superTypes") } // TODO find out how `doResume` gets removed if (declaration.symbol == context.ir.symbols.coroutineImpl) { declaration.declarations.forEach { if (it is IrSimpleFunction && it.name.asString() == "doResume") { - it.enqueue() + it.enqueue("hack for CoroutineImpl::doResume") } } } } - if (declaration is IrSimpleFunction) { - declaration.resolveFakeOverride()?.enqueue() + if (declaration is IrSimpleFunction && declaration.isFakeOverride) { + declaration.resolveFakeOverride()?.enqueue("real overridden fun") } // Collect instantiated classes. if (declaration is IrConstructor) { declaration.constructedClass.let { - it.enqueue() + it.enqueue("constructed class") constructedClasses += it } } @@ -193,19 +222,19 @@ fun usefulDeclarations(roots: Iterable, context: JsIrBackendConte override fun visitFunctionAccess(expression: IrFunctionAccessExpression) { super.visitFunctionAccess(expression) - expression.symbol.owner.enqueue() + expression.symbol.owner.enqueue("function access") } override fun visitVariableAccess(expression: IrValueAccessExpression) { super.visitVariableAccess(expression) - expression.symbol.owner.enqueue() + expression.symbol.owner.enqueue("variable access") } override fun visitFieldAccess(expression: IrFieldAccessExpression) { super.visitFieldAccess(expression) - expression.symbol.owner.enqueue() + expression.symbol.owner.enqueue("field access") } override fun visitCall(expression: IrCall) { @@ -215,28 +244,28 @@ fun usefulDeclarations(roots: Iterable, context: JsIrBackendConte context.intrinsics.jsBoxIntrinsic -> { val inlineClass = expression.getTypeArgument(0)!!.getInlinedClass()!! val constructor = inlineClass.declarations.filterIsInstance().single { it.isPrimary } - constructor.enqueue() + constructor.enqueue("intrinsic: jsBoxIntrinsic") } context.intrinsics.jsClass -> { - (expression.getTypeArgument(0)!!.classifierOrFail.owner as IrDeclaration).enqueue() + (expression.getTypeArgument(0)!!.classifierOrFail.owner as IrDeclaration).enqueue("intrinsic: jsClass") } context.intrinsics.jsObjectCreate.symbol -> { val classToCreate = expression.getTypeArgument(0)!!.classifierOrFail.owner as IrClass - classToCreate.enqueue() + classToCreate.enqueue("intrinsic: jsObjectCreate") constructedClasses += classToCreate } context.intrinsics.jsEquals -> { - equalsMethod.enqueue() + equalsMethod.enqueue("intrinsic: jsEquals") } context.intrinsics.jsToString -> { - toStringMethod.enqueue() + toStringMethod.enqueue("intrinsic: jsToString") } context.intrinsics.jsHashCode -> { - hashCodeMethod.enqueue() + hashCodeMethod.enqueue("intrinsic: jsHashCode") } context.intrinsics.jsPlus -> { if (expression.getValueArgument(0)?.type?.classOrNull == context.irBuiltIns.stringClass) { - toStringMethod.enqueue() + toStringMethod.enqueue("intrinsic: jsPlus") } } } @@ -245,17 +274,23 @@ fun usefulDeclarations(roots: Iterable, context: JsIrBackendConte override fun visitStringConcatenation(expression: IrStringConcatenation) { super.visitStringConcatenation(expression) - toStringMethod.enqueue() + toStringMethod.enqueue("string concatenation") } }) } - fun IrOverridableDeclaration<*>.overridesUsefulFunction(): Boolean { - return this.overriddenSymbols.any { - (it.owner as? IrOverridableDeclaration<*>)?.let { - it in result || it.overridesUsefulFunction() - } ?: false + fun IrOverridableDeclaration<*>.findOverriddenUsefulDeclaration(): IrOverridableDeclaration<*>? { + for (overriddenSymbol in this.overriddenSymbols) { + val overriddenDeclaration = overriddenSymbol.owner as? IrOverridableDeclaration<*> ?: continue + + if (overriddenDeclaration in result) return overriddenDeclaration + + overriddenDeclaration.findOverriddenUsefulDeclaration()?.let { + return it + } } + + return null } for (klass in constructedClasses) { @@ -263,12 +298,14 @@ fun usefulDeclarations(roots: Iterable, context: JsIrBackendConte for (declaration in ArrayList(klass.declarations)) { if (declaration in result) continue - if (declaration is IrOverridableDeclaration<*> && declaration.overridesUsefulFunction()) { - declaration.enqueue() + if (declaration is IrOverridableDeclaration<*>) { + declaration.findOverriddenUsefulDeclaration()?.let { + declaration.enqueue(it, "overrides useful declaration") + } } if (declaration is IrSimpleFunction && declaration.getJsNameOrKotlinName().asString() == "valueOf") { - declaration.enqueue() + declaration.enqueue(klass, "valueOf") } // A hack to support `toJson` and other js-specific members @@ -276,7 +313,7 @@ fun usefulDeclarations(roots: Iterable, context: JsIrBackendConte declaration is IrField && declaration.correspondingPropertySymbol?.owner?.getJsName() != null || declaration is IrSimpleFunction && declaration.correspondingPropertySymbol?.owner?.getJsName() != null ) { - declaration.enqueue() + declaration.enqueue(klass, "annotated by @JsName") } } @@ -284,8 +321,12 @@ fun usefulDeclarations(roots: Iterable, context: JsIrBackendConte for (declaration in ArrayList(klass.declarations)) { // TODO this is a hack. if (declaration is IrProperty) { - declaration.getter?.let { if (it.overridesUsefulFunction()) it.enqueue() } - declaration.setter?.let { if (it.overridesUsefulFunction()) it.enqueue() } + fun IrSimpleFunction.enqueue(description: String) { + findOverriddenUsefulDeclaration()?.let { enqueue(it, description) } + } + + declaration.getter?.enqueue("(getter) overrides useful declaration") + declaration.setter?.enqueue("(setter) overrides useful declaration") } } @@ -294,12 +335,16 @@ fun usefulDeclarations(roots: Iterable, context: JsIrBackendConte ArrayList(klass.declarations).forEach { // TODO: fix the heck // if (it is IrSimpleFunction && it.name.asString() == "doResume") { - it.enqueue() + it.enqueue(klass, "hack for CoroutineImpl::doResume") // } } } } } + if (PRINT_REACHABILITY_INFO) { + reachabilityInfo.forEach(::println) + } + return result } \ No newline at end of file