[K/JS] Make interface subtyping faster and lighter.

This commit is contained in:
Artem Kobzar
2022-09-19 10:26:11 +00:00
committed by Space
parent 03f83ff339
commit a368fc37c7
22 changed files with 572 additions and 287 deletions
@@ -8,12 +8,10 @@ package org.jetbrains.kotlin.ir.backend.js
import org.jetbrains.kotlin.builtins.PrimitiveType
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.backend.js.utils.getJsName
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.isLong
import org.jetbrains.kotlin.ir.util.constructors
@@ -109,6 +107,9 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
// RTTI:
val implementSymbol = getInternalFunction("implement")
val setMetadataForSymbol = getInternalFunction("setMetadataFor")
val isInterfaceSymbol = getInternalFunction("isInterface")
val isArraySymbol = getInternalFunction("isArray")
// val isCharSymbol = getInternalFunction("isChar")
@@ -155,6 +155,7 @@ class JsIrBackendContext(
val dynamicType: IrDynamicType = IrDynamicTypeImpl(null, emptyList(), Variance.INVARIANT)
val intrinsics: JsIntrinsics = JsIntrinsics(irBuiltIns, this)
override val reflectionSymbols: ReflectionSymbols get() = intrinsics.reflectionSymbols
override val propertyLazyInitialization: PropertyLazyInitialization = PropertyLazyInitialization(
@@ -526,7 +526,7 @@ private val privateMemberUsagesLoweringPhase = makeBodyLoweringPhase(
private val propertyReferenceLoweringPhase = makeBodyLoweringPhase(
::PropertyReferenceLowering,
name = "PropertyReferenceLowering",
description = "Transform property references"
description = "Transform property references",
)
private val interopCallableReferenceLoweringPhase = makeBodyLoweringPhase(
@@ -669,7 +669,7 @@ private val typeOperatorLoweringPhase = makeBodyLoweringPhase(
bridgesConstructionPhase,
removeInlineDeclarationsWithReifiedTypeParametersLoweringPhase,
singleAbstractMethodPhase, errorExpressionLoweringPhase,
interopCallableReferenceLoweringPhase
interopCallableReferenceLoweringPhase,
)
)
@@ -705,7 +705,6 @@ private val constLoweringPhase = makeBodyLoweringPhase(
name = "ConstLowering",
description = "Wrap Long and Char constants into constructor invocation"
)
private val inlineClassDeclarationLoweringPhase = makeDeclarationTransformerPhase(
{ InlineClassLowering(it).inlineClassDeclarationLowering },
name = "InlineClassDeclarationLowering",
@@ -806,6 +805,7 @@ private val implicitlyExportedDeclarationsMarkingLowering = makeDeclarationTrans
description = "Add @JsImplicitExport annotation to declarations which are not exported but are used inside other exported declarations as a type"
)
private val cleanupLoweringPhase = makeBodyLoweringPhase(
{ CleanupLowering() },
name = "CleanupLowering",
@@ -5,22 +5,17 @@
package org.jetbrains.kotlin.ir.backend.js.dce
import org.jetbrains.kotlin.backend.common.lower.MethodsFromAnyGeneratorForLowerings.Companion.collectOverridenSymbols
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.lower.isBuiltInClass
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.backend.js.utils.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrGetObjectValue
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
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.types.*
import org.jetbrains.kotlin.ir.util.*
internal class JsUsefulDeclarationProcessor(
@@ -33,6 +28,13 @@ internal class JsUsefulDeclarationProcessor(
private val hashCodeMethod = getMethodOfAny("hashCode")
override val bodyVisitor: BodyVisitorBase = object : BodyVisitorBase() {
override fun visitFunctionAccess(expression: IrFunctionAccessExpression, data: IrDeclaration) {
if (expression.symbol != context.intrinsics.implementSymbol) {
// Just ignore implement to not include large chunk of code inside small applications if it's not needed
super.visitFunctionAccess(expression, data)
}
}
override fun visitCall(expression: IrCall, data: IrDeclaration) {
super.visitCall(expression, data)
when (expression.symbol) {
@@ -113,20 +115,41 @@ internal class JsUsefulDeclarationProcessor(
}
override fun processSuperTypes(irClass: IrClass) {
irClass.superTypes.forEach {
if (!it.isInterface()) {
(it.classifierOrNull as? IrClassSymbol)?.owner?.enqueue(irClass, "superTypes")
}
}
}
override fun processClass(irClass: IrClass) {
super.processClass(irClass)
if (irClass.containsMetadata()) {
when {
irClass.isInterface -> context.intrinsics.metadataInterfaceConstructorSymbol.owner.enqueue(irClass, "interface metadata")
irClass.isObject -> context.intrinsics.metadataObjectConstructorSymbol.owner.enqueue(irClass, "object metadata")
irClass.isInterface -> {
context.intrinsics.implementSymbol.owner.enqueue(irClass, "interface metadata")
context.intrinsics.metadataInterfaceConstructorSymbol.owner.enqueue(irClass, "interface metadata")
}
else -> context.intrinsics.metadataClassConstructorSymbol.owner.enqueue(irClass, "class metadata")
}
context.intrinsics.setMetadataForSymbol.owner.enqueue(irClass, "metadata")
}
}
override fun processSimpleFunction(irFunction: IrSimpleFunction) {
super.processSimpleFunction(irFunction)
if (irFunction.isReal && irFunction.body != null) {
irFunction.parentClassOrNull?.takeIf { it.isInterface }?.enqueue(irFunction, "interface default method is used")
}
}
private fun IrClass.containsMetadata(): Boolean =
!isExternal && !isExpect && !isBuiltInClass(this)
!isExternal && !isExpect && !isBuiltInClass(this)
override fun processConstructedClassDeclaration(declaration: IrDeclaration) {
if (declaration in result) return
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.ir.backend.js.utils.isAssociatedObjectAnnotatedAnnot
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.IrType
import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
@@ -138,14 +139,12 @@ abstract class UsefulDeclarationProcessor(
protected val result = hashSetOf<IrDeclaration>()
protected val classesWithObjectAssociations = hashSetOf<IrClass>()
public val usefulPolyfilledDeclarations = hashSetOf<IrDeclaration>()
val usefulPolyfilledDeclarations = hashSetOf<IrDeclaration>()
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")
}
processSuperTypes(irClass)
if (irClass.isObject && isExported(irClass)) {
context.mapping.objectToGetInstanceFunction[irClass]
@@ -161,6 +160,12 @@ abstract class UsefulDeclarationProcessor(
}
}
protected open fun processSuperTypes(irClass: IrClass) {
irClass.superTypes.forEach {
(it.classifierOrNull as? IrClassSymbol)?.owner?.enqueue(irClass, "superTypes")
}
}
protected open fun processSimpleFunction(irFunction: IrSimpleFunction) {
if (irFunction.isFakeOverride) {
irFunction.resolveFakeOverride()?.enqueue(irFunction, "real overridden fun", isContagious = false)
@@ -13,6 +13,11 @@ 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.IrType
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.types.isAny
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
@@ -24,6 +29,8 @@ class UselessDeclarationsRemover(
private val context: JsIrBackendContext,
private val dceRuntimeDiagnostic: RuntimeDiagnostic?,
) : IrElementVisitorVoid {
private val savedTypesCache = hashMapOf<IrClassSymbol, Set<IrClassSymbol>>()
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
@@ -47,6 +54,23 @@ class UselessDeclarationsRemover(
if (removeUnusedAssociatedObjects && declaration.annotations.any { !it.shouldKeepAnnotation() }) {
declaration.annotations = declaration.annotations.filter { it.shouldKeepAnnotation() }
}
declaration.superTypes = declaration.superTypes
.flatMap { it.classOrNull?.collectUsedSuperTypes() ?: emptyList() }
.distinct()
.map { it.defaultType }
}
private fun IrClassSymbol.collectUsedSuperTypes(): Set<IrClassSymbol> {
return savedTypesCache.getOrPut(this) {
if (owner in usefulDeclarations) {
setOf(this)
} else {
owner.superTypes
.flatMap { it.takeIf { !it.isAny() }?.classOrNull?.collectUsedSuperTypes() ?: emptyList() }
.toSet()
}
}
}
// TODO bring back the primary constructor fix
@@ -128,8 +128,8 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
override fun visitGetObjectValue(expression: IrGetObjectValue, context: JsGenerationContext): JsExpression {
val obj = expression.symbol.owner
assert(obj.kind == ClassKind.OBJECT)
assert(obj.isEffectivelyExternal()) { "Non external IrGetObjectValue must be lowered" }
assert(obj.kind == ClassKind.OBJECT)
assert(obj.isEffectivelyExternal()) { "Non external IrGetObjectValue must be lowered" }
return context.getRefForExternalClass(obj).withSource(expression, context)
}
@@ -269,7 +269,7 @@ class IrModuleToJsTransformerTmp(
staticContext.classModels.entries.forEach { (symbol, model) ->
result.classes[nameGenerator.getNameForClass(symbol.owner)] =
JsIrIcClassModel(model.klass.superTypes.map { staticContext.getNameForClass((it.classifierOrFail as IrClassSymbol).owner) }).also {
JsIrIcClassModel(model.superClasses.map { staticContext.getNameForClass(it.owner) }).also {
it.preDeclarationBlock.statements += model.preDeclarationBlock.statements
it.postDeclarationBlock.statements += model.postDeclarationBlock.statements
}
@@ -6,27 +6,31 @@
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
import org.jetbrains.kotlin.backend.common.compilationException
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.export.isAllowedFakeOverriddenDeclaration
import org.jetbrains.kotlin.ir.backend.js.export.isExported
import org.jetbrains.kotlin.ir.backend.js.export.isOverriddenExported
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrClassReference
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrVarargImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classifierOrFail
import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.types.isAny
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.common.isValidES5Identifier
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.runIf
class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationContext) {
private val className = context.getNameForClass(irClass)
private val classNameRef = className.makeRef()
private val baseClass: IrType? = irClass.superTypes.firstOrNull { !it.classifierOrFail.isInterface }
@@ -44,6 +48,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
assert(!irClass.isExpect)
if (!es6mode) maybeGeneratePrimaryConstructor()
val transformer = IrDeclarationToJsTransformer()
// Properties might be lowered out of classes
@@ -74,7 +79,6 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
}
} else {
classBlock.statements += declaration.accept(transformer, context)
classModel.preDeclarationBlock.statements += generateInheritanceCode()
}
}
is IrSimpleFunction -> {
@@ -104,8 +108,6 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
}
}
classBlock.statements += generateClassMetadata()
if (!irClass.isInterface) {
for (property in properties) {
if (property.getter?.extensionReceiverParameter != null || property.setter?.extensionReceiverParameter != null)
@@ -206,7 +208,11 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
}
}
}
classModel.preDeclarationBlock.statements += generateSetMetadataCall()
context.staticContext.classModels[irClass.symbol] = classModel
return classBlock
}
@@ -307,24 +313,49 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
val func = JsFunction(emptyScope, JsBlock(), "Ctor for ${irClass.name}")
func.name = className
classBlock.statements += func.makeStmt()
classModel.preDeclarationBlock.statements += generateInheritanceCode()
}
}
private fun generateInheritanceCode(): List<JsStatement> {
val baseClassPrototype = baseClassRef ?: return emptyList()
private fun generateSetMetadataCall(): JsStatement {
val setMetadataFor = context.staticContext.backendContext.intrinsics.setMetadataForSymbol.owner
val createCall = jsAssignment(
classPrototypeRef, JsInvocation(Namer.JS_OBJECT_CREATE_FUNCTION, prototypeOf(baseClassPrototype))
val ctor = classNameRef
val parent = baseClassRef
val name = generateSimpleName()
val interfaces = generateInterfacesList()
val metadataConstructor = getMetadataConstructor()
val associatedObjectKey = generateAssociatedObjectKey()
val associatedObjects = generateAssociatedObjects()
val suspendArity = generateSuspendArity()
return JsInvocation(
JsNameRef(context.getNameForStaticFunction(setMetadataFor)),
listOf(ctor, name, metadataConstructor, parent, interfaces, associatedObjectKey, associatedObjects, suspendArity)
.dropLastWhile { it == null }
.map { it ?: Namer.JS_UNDEFINED }
).makeStmt()
val ctorAssign = jsAssignment(JsNameRef(Namer.CONSTRUCTOR_NAME, classPrototypeRef), classNameRef).makeStmt()
return listOf(createCall, ctorAssign)
}
private fun generateClassMetadata(): JsStatement {
val metadataConstructor = with(context.staticContext.backendContext.intrinsics) {
private fun IrType.asConstructorRef(): JsNameRef? {
val ownerSymbol = classOrNull?.takeIf {
!isAny() && !isFunctionType() && !it.owner.isEffectivelyExternal()
} ?: return null
return JsNameRef(context.getNameForClass(ownerSymbol.owner))
}
private fun IrType.isFunctionType() = isFunctionOrKFunction() || isSuspendFunctionOrKFunction()
private fun isCoroutineClass(): Boolean = irClass.superTypes.any { it.isSuspendFunctionTypeOrSubtype() }
private fun generateSimpleName(): JsStringLiteral? {
return irClass.name.takeIf { !it.isSpecial }?.let { JsStringLiteral(it.identifier) }
}
private fun getMetadataConstructor(): JsNameRef {
val metadataConstructorSymbol = with(context.staticContext.backendContext.intrinsics) {
when {
irClass.isInterface -> metadataInterfaceConstructorSymbol
irClass.isObject -> metadataObjectConstructorSymbol
@@ -332,32 +363,21 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
}
}
val simpleName = irClass.name
.takeIf { !it.isSpecial }
?.let { JsStringLiteral(it.identifier) }
val interfaces = generateSuperClasses()
val associatedObjectKey = generateAssociatedObjectKey()
val associatedObjects = generateAssociatedObjects()
val fastPrototype = generateFastPrototype()
val suspendArity = generateSuspendArity()
val constructorCall = JsInvocation(
JsNameRef(context.getNameForStaticFunction(metadataConstructor.owner)),
listOf(simpleName, interfaces, associatedObjectKey, associatedObjects, suspendArity, fastPrototype)
.dropLastWhile { it == null }
.map { it ?: Namer.JS_UNDEFINED }
)
return jsAssignment(JsNameRef(Namer.METADATA, classNameRef), constructorCall).makeStmt()
return JsNameRef(context.getNameForStaticFunction(metadataConstructorSymbol.owner))
}
private fun isCoroutineClass(): Boolean = irClass.superTypes.any { it.isSuspendFunctionTypeOrSubtype() }
private fun generateInterfacesList(): JsArrayLiteral? {
val listRef = irClass.superTypes
.filter { it.classOrNull?.owner?.isExternal != true }
.takeIf { it.size > 1 || it.singleOrNull() != baseClass }
?.mapNotNull { it.asConstructorRef() }
?.takeIf { it.isNotEmpty() } ?: return null
return JsArrayLiteral(listRef)
}
private fun generateSuspendArity(): JsArrayLiteral? {
if (!isCoroutineClass()) return null
val arity = context.staticContext.backendContext.mapping.suspendArityStore[irClass]!!
val invokeFunctions = context.staticContext.backendContext.mapping.suspendArityStore[irClass] ?: return null
val arity = invokeFunctions
.map { it.valueParameters.size }
.distinct()
.map { JsIntLiteral(it) }
@@ -365,30 +385,6 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
return JsArrayLiteral(arity)
}
private fun generateSuperClasses(): JsArrayLiteral? {
val parentSymbols = irClass.superTypes.mapNotNull {
val symbol = it.classifierOrFail as IrClassSymbol
val isFunctionType = it.isFunctionType()
// TODO: make sure that there is a test which breaks when isExternal is used here instead of isEffectivelyExternal
val requireInMetadata = if (context.staticContext.backendContext.baseClassIntoMetadata)
!it.isAny()
else
symbol.isInterface
if (requireInMetadata && !isFunctionType && !symbol.isEffectivelyExternal) {
symbol
} else null
}
return parentSymbols
.takeIf { it.isNotEmpty() }
?.run { JsArrayLiteral(map { JsNameRef(context.getNameForClass(it.owner)) }) }
}
private fun generateFastPrototype() = baseClassRef?.let { prototypeOf(it) }
private fun IrType.isFunctionType() = isFunctionOrKFunction() || isSuspendFunctionOrKFunction()
private fun generateAssociatedObjectKey(): JsIntLiteral? {
return context.getAssociatedObjectKey(irClass)?.let { JsIntLiteral(it) }
}
@@ -440,10 +436,9 @@ private fun IrOverridableDeclaration<*>.overridesExternal(): Boolean {
}
private val IrClassifierSymbol.isInterface get() = (owner as? IrClass)?.isInterface == true
private val IrClassifierSymbol.isEffectivelyExternal get() = (owner as? IrDeclaration)?.isEffectivelyExternal() == true
class JsIrClassModel(val klass: IrClass) {
val superClasses = klass.superTypes.map { it.classifierOrFail as IrClassSymbol }
val superClasses = klass.superTypes.map { it.classifierOrNull as IrClassSymbol }
val preDeclarationBlock = JsCompositeBlock()
val postDeclarationBlock = JsCompositeBlock()
@@ -272,7 +272,7 @@ class Merger(
DFS.dfs(
classModelMap.keys,
{ klass -> classModelMap[klass]?.superClasses ?: emptyList() },
{ classModelMap[it]?.superClasses ?: emptyList() },
declarationHandler
)
}
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.ir.declarations.IrOverridableDeclaration
import org.jetbrains.kotlin.ir.expressions.IrClassReference
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.IrVararg
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName
@@ -36,8 +37,8 @@ fun IrConstructorCall.getSingleConstStringArgument() =
(getValueArgument(0) as IrConst<String>).value
@Suppress("UNCHECKED_CAST")
fun IrConstructorCall.getSingleConstBooleanArgument() =
(getValueArgument(0) as IrConst<Boolean>).value
fun IrConstructorCall.getClassReferencVarargArguments() =
(getValueArgument(0) as? IrVararg)?.elements as? List<IrClassReference>
fun IrAnnotationContainer.getJsModule(): String? =
getAnnotation(JsAnnotations.jsModuleFqn)?.getSingleConstStringArgument()
@@ -32,7 +32,7 @@ object Namer {
val JS_OBJECT_CREATE_FUNCTION = JsNameRef("create", JS_OBJECT)
val METADATA = "\$metadata\$"
val METADATA_INTERFACE_ID = "interfaceId"
val INTERFACES_MASK = "\$imask\$"
val KCALLABLE_GET_NAME = "<get-name>"
val KCALLABLE_NAME = "callableName"