[WASM] DCE implementation
This commit is contained in:
committed by
TeamCityServer
parent
84caad7ba2
commit
2ec0411a7f
@@ -328,6 +328,7 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
|
||||
exportedDeclarations = setOf(FqName("main")),
|
||||
emitNameSection = arguments.wasmDebug,
|
||||
propertyLazyInitialization = arguments.irPropertyLazyInitialization,
|
||||
dceEnabled = arguments.irDce,
|
||||
)
|
||||
val outputWasmFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "wasm")!!
|
||||
outputWasmFile.writeBytes(res.wasm)
|
||||
|
||||
@@ -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<IrModuleFragment>,
|
||||
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<IrDeclaration>, 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<IrModuleFragment>, context: JsIrBackendContext): Iterable<IrDeclaration> {
|
||||
val rootDeclarations = mutableListOf<IrDeclaration>()
|
||||
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<IrModuleFragment>,
|
||||
usefulDeclarations: Set<IrDeclaration>,
|
||||
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<IrDeclaration>? {
|
||||
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<IrDeclaration>,
|
||||
context: JsIrBackendContext,
|
||||
removeUnusedAssociatedObjects: Boolean,
|
||||
): Set<IrDeclaration> {
|
||||
val printReachabilityInfo =
|
||||
context.configuration.getBoolean(JSConfigurationKeys.PRINT_REACHABILITY_INFO) ||
|
||||
java.lang.Boolean.getBoolean("kotlin.js.ir.dce.print.reachability.info")
|
||||
val reachabilityInfo: MutableSet<String> = if (printReachabilityInfo) linkedSetOf() else Collections.emptySet()
|
||||
|
||||
val queue = ArrayDeque<IrDeclaration>()
|
||||
val result = hashSetOf<IrDeclaration>()
|
||||
|
||||
// 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<IrOverridableDeclaration<*>>()
|
||||
val constructedClasses = hashSetOf<IrClass>()
|
||||
|
||||
val classesWithObjectAssociations = hashSetOf<IrClass>()
|
||||
val referencedJsClasses = hashSetOf<IrDeclaration>()
|
||||
val referencedJsClassesFromExpressions = hashSetOf<IrClass>()
|
||||
|
||||
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 ?: "<unknown>"
|
||||
val toFqn = (this as? IrDeclarationWithName)?.fqNameWhenAvailable?.asString() ?: "<unknown>"
|
||||
|
||||
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 = "<ROOT>")
|
||||
}
|
||||
|
||||
// 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<IrFunction>().single { it.name.asString() == "toString" }
|
||||
val equalsMethod =
|
||||
context.irBuiltIns.anyClass.owner.declarations.filterIsInstance<IrFunction>().single { it.name.asString() == "equals" }
|
||||
val hashCodeMethod =
|
||||
context.irBuiltIns.anyClass.owner.declarations.filterIsInstance<IrFunction>().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<IrConstructor>().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 = "<ROOT>") }
|
||||
}
|
||||
|
||||
if (printReachabilityInfo) {
|
||||
reachabilityInfo.forEach(::println)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun Collection<IrClass>.filterDescendantsOf(bases: Collection<IrClass>): Collection<IrClass> {
|
||||
val visited = hashSetOf<IrClass>()
|
||||
val baseDescendants = hashSetOf<IrClass>()
|
||||
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) }
|
||||
}
|
||||
@@ -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<IrModuleFragment>,
|
||||
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<IrModuleFragment>, context: JsIrBackendContext): List<IrDeclaration> = 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
|
||||
+181
@@ -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<IrConstructor>().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<IrDeclaration>()
|
||||
private val referencedJsClassesFromExpressions = hashSetOf<IrClass>()
|
||||
|
||||
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<IrClass>.filterDescendantsOf(bases: Collection<IrClass>): Collection<IrClass> {
|
||||
val visited = hashSetOf<IrClass>()
|
||||
val baseDescendants = hashSetOf<IrClass>()
|
||||
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) }
|
||||
}
|
||||
+235
@@ -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<IrFunction>().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<Unit, IrDeclaration> {
|
||||
|
||||
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() ?: "<unknown>"
|
||||
val toFqn = (to as? IrDeclarationWithName)?.fqNameWhenAvailable?.asString() ?: "<unknown>"
|
||||
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<IrOverridableDeclaration<*>>()
|
||||
protected val constructedClasses = hashSetOf<IrClass>()
|
||||
private val reachabilityInfo: MutableSet<String> = if (printReachabilityInfo) linkedSetOf() else Collections.emptySet()
|
||||
private val queue = ArrayDeque<IrDeclaration>()
|
||||
protected val result = hashSetOf<IrDeclaration>()
|
||||
protected val classesWithObjectAssociations = hashSetOf<IrClass>()
|
||||
|
||||
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<IrDeclaration>): Set<IrDeclaration> {
|
||||
|
||||
rootDeclarations.forEach {
|
||||
it.enqueue(it, "<ROOT>")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
+108
@@ -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<IrDeclaration>,
|
||||
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<IrDeclaration>? {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
@@ -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.*
|
||||
|
||||
@@ -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<FqName> = 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()
|
||||
|
||||
@@ -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<IrModuleFragment>, 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<IrModuleFragment>, context: WasmBackendContext): List<IrDeclaration> = 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<IrModuleFragment>.onAllFiles(body: IrFile.() -> Unit) {
|
||||
forEach { module ->
|
||||
module.files.forEach { file ->
|
||||
file.body()
|
||||
}
|
||||
}
|
||||
}
|
||||
+205
@@ -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<IrConstructor>()
|
||||
?.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()
|
||||
}
|
||||
+44
@@ -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<IrDeclaration>
|
||||
) : 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -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!!)
|
||||
|
||||
+9
-7
@@ -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
|
||||
|
||||
+15
-8
@@ -74,7 +74,7 @@ class WasmCompiledModuleFragment(val irBuiltIns: IrBuiltIns) {
|
||||
ReferencableElements<InterfaceImplementation, Int>()
|
||||
|
||||
val interfaceImplementationsMethods =
|
||||
LinkedHashMap<InterfaceImplementation, Map<IrFunctionSymbol, WasmSymbol<WasmFunction>>>()
|
||||
LinkedHashMap<InterfaceImplementation, Map<IrFunctionSymbol, WasmSymbol<WasmFunction>?>>()
|
||||
|
||||
val exports = mutableListOf<WasmExport<*>>()
|
||||
|
||||
@@ -83,6 +83,7 @@ class WasmCompiledModuleFragment(val irBuiltIns: IrBuiltIns) {
|
||||
val jsFuns = mutableListOf<JsCodeSnippet>()
|
||||
|
||||
class FunWithPriority(val function: WasmFunction, val priority: String)
|
||||
|
||||
val initFunctions = mutableListOf<FunWithPriority>()
|
||||
|
||||
val scratchMemAddr = WasmSymbol<Int>()
|
||||
@@ -229,28 +230,34 @@ class WasmCompiledModuleFragment(val irBuiltIns: IrBuiltIns) {
|
||||
)
|
||||
|
||||
val interfaceTableElementsLists = interfaceMethodTables.defined.keys.associateWith {
|
||||
mutableMapOf<Int, WasmSymbol<WasmFunction>>()
|
||||
mutableMapOf<Int, WasmSymbol<WasmFunction>?>()
|
||||
}
|
||||
|
||||
interfaceImplementationIds.forEach { ii: InterfaceImplementation, implId: Int ->
|
||||
for ((interfaceFunction: IrFunctionSymbol, wasmFunction: WasmSymbol<WasmFunction>) in interfaceImplementationsMethods[ii]!!) {
|
||||
for ((ii: InterfaceImplementation, implId: Int) in interfaceImplementationIds) {
|
||||
for ((interfaceFunction: IrFunctionSymbol, wasmFunction: WasmSymbol<WasmFunction>?) 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)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ interface WasmModuleCodegenContext : WasmBaseCodegenContext {
|
||||
|
||||
fun registerInterfaceImplementationMethod(
|
||||
interfaceImplementation: InterfaceImplementation,
|
||||
table: Map<IrFunctionSymbol, WasmSymbol<WasmFunction>>,
|
||||
table: Map<IrFunctionSymbol, WasmSymbol<WasmFunction>?>,
|
||||
)
|
||||
|
||||
fun referenceInterfaceImplementationId(interfaceImplementation: InterfaceImplementation): WasmSymbol<Int>
|
||||
|
||||
+1
-1
@@ -126,7 +126,7 @@ class WasmModuleCodegenContextImpl(
|
||||
|
||||
override fun registerInterfaceImplementationMethod(
|
||||
interfaceImplementation: InterfaceImplementation,
|
||||
table: Map<IrFunctionSymbol, WasmSymbol<WasmFunction>>
|
||||
table: Map<IrFunctionSymbol, WasmSymbol<WasmFunction>?>
|
||||
) {
|
||||
wasmFragment.interfaceImplementationsMethods[interfaceImplementation] = table
|
||||
}
|
||||
|
||||
+5
-3
@@ -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) {
|
||||
|
||||
+2
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// EXPECTED_REACHABLE_NODES: 1281
|
||||
// IGNORE_BACKEND: WASM
|
||||
// WASM_MUTE_REASON: LAZY_INIT_PROPERTIES
|
||||
package foo
|
||||
|
||||
external interface Chrome {
|
||||
|
||||
Reference in New Issue
Block a user