[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")),
|
exportedDeclarations = setOf(FqName("main")),
|
||||||
emitNameSection = arguments.wasmDebug,
|
emitNameSection = arguments.wasmDebug,
|
||||||
propertyLazyInitialization = arguments.irPropertyLazyInitialization,
|
propertyLazyInitialization = arguments.irPropertyLazyInitialization,
|
||||||
|
dceEnabled = arguments.irDce,
|
||||||
)
|
)
|
||||||
val outputWasmFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "wasm")!!
|
val outputWasmFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "wasm")!!
|
||||||
outputWasmFile.writeBytes(res.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.CompilationOutputs
|
||||||
import org.jetbrains.kotlin.ir.backend.js.CompilerResult
|
import org.jetbrains.kotlin.ir.backend.js.CompilerResult
|
||||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
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.ExportModelGenerator
|
||||||
import org.jetbrains.kotlin.ir.backend.js.export.ExportModelToJsStatements
|
import org.jetbrains.kotlin.ir.backend.js.export.ExportModelToJsStatements
|
||||||
import org.jetbrains.kotlin.ir.backend.js.export.ExportedModule
|
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.CommonConfigurationKeys
|
||||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||||
import org.jetbrains.kotlin.ir.backend.js.*
|
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.export.*
|
||||||
import org.jetbrains.kotlin.ir.backend.js.lower.StaticMembersLowering
|
import org.jetbrains.kotlin.ir.backend.js.lower.StaticMembersLowering
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.*
|
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.PhaseConfig
|
||||||
import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel
|
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.WasmCompiledModuleFragment
|
||||||
import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmModuleFragmentGenerator
|
import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmModuleFragmentGenerator
|
||||||
import org.jetbrains.kotlin.backend.wasm.lower.markExportedDeclarations
|
import org.jetbrains.kotlin.backend.wasm.lower.markExportedDeclarations
|
||||||
@@ -31,6 +32,7 @@ fun compileWasm(
|
|||||||
exportedDeclarations: Set<FqName> = emptySet(),
|
exportedDeclarations: Set<FqName> = emptySet(),
|
||||||
propertyLazyInitialization: Boolean,
|
propertyLazyInitialization: Boolean,
|
||||||
emitNameSection: Boolean = false,
|
emitNameSection: Boolean = false,
|
||||||
|
dceEnabled: Boolean = false,
|
||||||
): WasmCompilerResult {
|
): WasmCompilerResult {
|
||||||
val mainModule = depsDescriptors.mainModule
|
val mainModule = depsDescriptors.mainModule
|
||||||
val configuration = depsDescriptors.compilerConfiguration
|
val configuration = depsDescriptors.compilerConfiguration
|
||||||
@@ -69,8 +71,12 @@ fun compileWasm(
|
|||||||
|
|
||||||
wasmPhases.invokeToplevel(phaseConfig, context, moduleFragment)
|
wasmPhases.invokeToplevel(phaseConfig, context, moduleFragment)
|
||||||
|
|
||||||
|
if (dceEnabled) {
|
||||||
|
eliminateDeadDeclarations(listOf(moduleFragment), context)
|
||||||
|
}
|
||||||
|
|
||||||
val compiledWasmModule = WasmCompiledModuleFragment(context.irBuiltIns)
|
val compiledWasmModule = WasmCompiledModuleFragment(context.irBuiltIns)
|
||||||
val codeGenerator = WasmModuleFragmentGenerator(context, compiledWasmModule)
|
val codeGenerator = WasmModuleFragmentGenerator(context, compiledWasmModule, allowIncompleteImplementations = dceEnabled)
|
||||||
codeGenerator.generateModule(moduleFragment)
|
codeGenerator.generateModule(moduleFragment)
|
||||||
|
|
||||||
val linkedModule = compiledWasmModule.linkWasmCompiledFragments()
|
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 (tryToGenerateIntrinsicCall(call, function)) {
|
||||||
if (function.returnType == irBuiltIns.unitType)
|
if (function.returnType == irBuiltIns.unitType)
|
||||||
body.buildGetUnit()
|
body.buildGetUnit()
|
||||||
@@ -284,7 +283,7 @@ class BodyGenerator(val context: WasmFunctionCodegenContext) : IrElementVisitorV
|
|||||||
val klass = function.parentAsClass
|
val klass = function.parentAsClass
|
||||||
if (!klass.isInterface) {
|
if (!klass.isInterface) {
|
||||||
val classMetadata = context.getClassMetadata(klass.symbol)
|
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
|
// Dispatch receiver should be simple and without side effects at this point
|
||||||
// TODO: Verify
|
// TODO: Verify
|
||||||
generateExpression(call.dispatchReceiver!!)
|
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.name.parentOrNull
|
||||||
import org.jetbrains.kotlin.wasm.ir.*
|
import org.jetbrains.kotlin.wasm.ir.*
|
||||||
|
|
||||||
class DeclarationGenerator(val context: WasmModuleCodegenContext) : IrElementVisitorVoid {
|
class DeclarationGenerator(val context: WasmModuleCodegenContext, private val allowIncompleteImplementations: Boolean) : IrElementVisitorVoid {
|
||||||
|
|
||||||
// Shortcuts
|
// Shortcuts
|
||||||
private val backendContext: WasmBackendContext = context.backendContext
|
private val backendContext: WasmBackendContext = context.backendContext
|
||||||
@@ -238,7 +238,6 @@ class DeclarationGenerator(val context: WasmModuleCodegenContext) : IrElementVis
|
|||||||
wasmExpressionGenerator.buildRttCanon(wasmGcType)
|
wasmExpressionGenerator.buildRttCanon(wasmGcType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val rtt = WasmGlobal(
|
val rtt = WasmGlobal(
|
||||||
name = "rtt_of_$nameStr",
|
name = "rtt_of_$nameStr",
|
||||||
isMutable = false,
|
isMutable = false,
|
||||||
@@ -258,13 +257,16 @@ class DeclarationGenerator(val context: WasmModuleCodegenContext) : IrElementVis
|
|||||||
// TODO: Cache it
|
// TODO: Cache it
|
||||||
val interfaceMetadata = InterfaceMetadata(i, irBuiltIns)
|
val interfaceMetadata = InterfaceMetadata(i, irBuiltIns)
|
||||||
val table = interfaceMetadata.methods.associate { method ->
|
val table = interfaceMetadata.methods.associate { method ->
|
||||||
val classMethod: VirtualMethodMetadata =
|
val classMethod: VirtualMethodMetadata? = metadata.virtualMethods
|
||||||
metadata.virtualMethods
|
.find { it.signature == method.signature && it.function.modality != Modality.ABSTRACT } // TODO: Use map
|
||||||
.find { it.signature == method.signature } // TODO: Use map
|
|
||||||
?: error("Cannot find class implementation of method ${method.signature} in class ${declaration.fqNameWhenAvailable}")
|
|
||||||
|
|
||||||
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(
|
context.registerInterfaceImplementationMethod(
|
||||||
interfaceImplementation,
|
interfaceImplementation,
|
||||||
table
|
table
|
||||||
|
|||||||
+15
-8
@@ -74,7 +74,7 @@ class WasmCompiledModuleFragment(val irBuiltIns: IrBuiltIns) {
|
|||||||
ReferencableElements<InterfaceImplementation, Int>()
|
ReferencableElements<InterfaceImplementation, Int>()
|
||||||
|
|
||||||
val interfaceImplementationsMethods =
|
val interfaceImplementationsMethods =
|
||||||
LinkedHashMap<InterfaceImplementation, Map<IrFunctionSymbol, WasmSymbol<WasmFunction>>>()
|
LinkedHashMap<InterfaceImplementation, Map<IrFunctionSymbol, WasmSymbol<WasmFunction>?>>()
|
||||||
|
|
||||||
val exports = mutableListOf<WasmExport<*>>()
|
val exports = mutableListOf<WasmExport<*>>()
|
||||||
|
|
||||||
@@ -83,6 +83,7 @@ class WasmCompiledModuleFragment(val irBuiltIns: IrBuiltIns) {
|
|||||||
val jsFuns = mutableListOf<JsCodeSnippet>()
|
val jsFuns = mutableListOf<JsCodeSnippet>()
|
||||||
|
|
||||||
class FunWithPriority(val function: WasmFunction, val priority: String)
|
class FunWithPriority(val function: WasmFunction, val priority: String)
|
||||||
|
|
||||||
val initFunctions = mutableListOf<FunWithPriority>()
|
val initFunctions = mutableListOf<FunWithPriority>()
|
||||||
|
|
||||||
val scratchMemAddr = WasmSymbol<Int>()
|
val scratchMemAddr = WasmSymbol<Int>()
|
||||||
@@ -229,28 +230,34 @@ class WasmCompiledModuleFragment(val irBuiltIns: IrBuiltIns) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
val interfaceTableElementsLists = interfaceMethodTables.defined.keys.associateWith {
|
val interfaceTableElementsLists = interfaceMethodTables.defined.keys.associateWith {
|
||||||
mutableMapOf<Int, WasmSymbol<WasmFunction>>()
|
mutableMapOf<Int, WasmSymbol<WasmFunction>?>()
|
||||||
}
|
}
|
||||||
|
|
||||||
interfaceImplementationIds.forEach { ii: InterfaceImplementation, implId: Int ->
|
for ((ii: InterfaceImplementation, implId: Int) in interfaceImplementationIds) {
|
||||||
for ((interfaceFunction: IrFunctionSymbol, wasmFunction: WasmSymbol<WasmFunction>) in interfaceImplementationsMethods[ii]!!) {
|
for ((interfaceFunction: IrFunctionSymbol, wasmFunction: WasmSymbol<WasmFunction>?) in interfaceImplementationsMethods[ii]!!) {
|
||||||
interfaceTableElementsLists[interfaceFunction]!![implId] = wasmFunction
|
interfaceTableElementsLists[interfaceFunction]!![implId] = wasmFunction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val interfaceTableElements = interfaceTableElementsLists.map { (interfaceFunction, methods) ->
|
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 functions = MutableList(methods.size) { idx ->
|
||||||
val wasmFunc = methods[idx]!!
|
val wasmFunc = methods[idx]
|
||||||
val expression = buildWasmExpression {
|
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)
|
WasmTable.Value.Expression(expression)
|
||||||
}
|
}
|
||||||
WasmElement(
|
WasmElement(
|
||||||
type,
|
type,
|
||||||
values = functions,
|
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(
|
fun registerInterfaceImplementationMethod(
|
||||||
interfaceImplementation: InterfaceImplementation,
|
interfaceImplementation: InterfaceImplementation,
|
||||||
table: Map<IrFunctionSymbol, WasmSymbol<WasmFunction>>,
|
table: Map<IrFunctionSymbol, WasmSymbol<WasmFunction>?>,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun referenceInterfaceImplementationId(interfaceImplementation: InterfaceImplementation): WasmSymbol<Int>
|
fun referenceInterfaceImplementationId(interfaceImplementation: InterfaceImplementation): WasmSymbol<Int>
|
||||||
|
|||||||
+1
-1
@@ -126,7 +126,7 @@ class WasmModuleCodegenContextImpl(
|
|||||||
|
|
||||||
override fun registerInterfaceImplementationMethod(
|
override fun registerInterfaceImplementationMethod(
|
||||||
interfaceImplementation: InterfaceImplementation,
|
interfaceImplementation: InterfaceImplementation,
|
||||||
table: Map<IrFunctionSymbol, WasmSymbol<WasmFunction>>
|
table: Map<IrFunctionSymbol, WasmSymbol<WasmFunction>?>
|
||||||
) {
|
) {
|
||||||
wasmFragment.interfaceImplementationsMethods[interfaceImplementation] = table
|
wasmFragment.interfaceImplementationsMethods[interfaceImplementation] = table
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-3
@@ -13,14 +13,16 @@ import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
|||||||
|
|
||||||
class WasmModuleFragmentGenerator(
|
class WasmModuleFragmentGenerator(
|
||||||
backendContext: WasmBackendContext,
|
backendContext: WasmBackendContext,
|
||||||
wasmModuleFragment: WasmCompiledModuleFragment
|
wasmModuleFragment: WasmCompiledModuleFragment,
|
||||||
|
allowIncompleteImplementations: Boolean,
|
||||||
) {
|
) {
|
||||||
private val declarationGenerator =
|
private val declarationGenerator =
|
||||||
DeclarationGenerator(
|
DeclarationGenerator(
|
||||||
WasmModuleCodegenContextImpl(
|
WasmModuleCodegenContextImpl(
|
||||||
backendContext,
|
backendContext,
|
||||||
wasmModuleFragment
|
wasmModuleFragment,
|
||||||
)
|
),
|
||||||
|
allowIncompleteImplementations
|
||||||
)
|
)
|
||||||
|
|
||||||
fun generateModule(irModuleFragment: IrModuleFragment) {
|
fun generateModule(irModuleFragment: IrModuleFragment) {
|
||||||
|
|||||||
+2
@@ -2,6 +2,8 @@
|
|||||||
// FIR status: not supported in JVM
|
// FIR status: not supported in JVM
|
||||||
// IGNORE_BACKEND: JVM, JVM_IR
|
// IGNORE_BACKEND: JVM, JVM_IR
|
||||||
// IGNORE_BACKEND: NATIVE
|
// IGNORE_BACKEND: NATIVE
|
||||||
|
// IGNORE_BACKEND: WASM
|
||||||
|
// WASM_MUTE_REASON: LAZY_INIT_PROPERTIES
|
||||||
// IGNORE_LIGHT_ANALYSIS
|
// IGNORE_LIGHT_ANALYSIS
|
||||||
// MODULE: lib1
|
// MODULE: lib1
|
||||||
// FILE: lib1.kt
|
// 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.JsGenerationGranularity
|
||||||
import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationOptions
|
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.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.lower.serialization.ir.JsManglerDesc
|
||||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer
|
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer
|
||||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformerTmp
|
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.psi.KtPsiFactory
|
||||||
import org.jetbrains.kotlin.resolve.CompilerEnvironment
|
import org.jetbrains.kotlin.resolve.CompilerEnvironment
|
||||||
import org.jetbrains.kotlin.test.*
|
import org.jetbrains.kotlin.test.*
|
||||||
import org.jetbrains.kotlin.test.directives.JsEnvironmentConfigurationDirectives
|
|
||||||
import org.jetbrains.kotlin.test.util.KtTestUtil
|
import org.jetbrains.kotlin.test.util.KtTestUtil
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -176,15 +175,14 @@ abstract class BasicWasmBoxTest(
|
|||||||
AnalyzerWithCompilerReport(config.configuration)
|
AnalyzerWithCompilerReport(config.configuration)
|
||||||
)
|
)
|
||||||
|
|
||||||
val directives = KotlinTestUtils.parseDirectives(FileUtil.loadFile(testFile))
|
|
||||||
|
|
||||||
val compilerResult = compileWasm(
|
val compilerResult = compileWasm(
|
||||||
sourceModule,
|
sourceModule,
|
||||||
phaseConfig = phaseConfig,
|
phaseConfig = phaseConfig,
|
||||||
irFactory = IrFactoryImpl,
|
irFactory = IrFactoryImpl,
|
||||||
exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))),
|
exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))),
|
||||||
propertyLazyInitialization = directives.contains(JsEnvironmentConfigurationDirectives.PROPERTY_LAZY_INITIALIZATION.name),
|
propertyLazyInitialization = true,
|
||||||
emitNameSection = true,
|
emitNameSection = true,
|
||||||
|
dceEnabled = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
outputWatFile.write(compilerResult.wat)
|
outputWatFile.write(compilerResult.wat)
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
// EXPECTED_REACHABLE_NODES: 1281
|
// EXPECTED_REACHABLE_NODES: 1281
|
||||||
|
// IGNORE_BACKEND: WASM
|
||||||
|
// WASM_MUTE_REASON: LAZY_INIT_PROPERTIES
|
||||||
package foo
|
package foo
|
||||||
|
|
||||||
external interface Chrome {
|
external interface Chrome {
|
||||||
|
|||||||
Reference in New Issue
Block a user