diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt index c52187b2015..1b763d6e77f 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt @@ -7,10 +7,12 @@ package org.jetbrains.kotlin.ir.backend.js import org.jetbrains.kotlin.builtins.PrimitiveType import org.jetbrains.kotlin.ir.IrBuiltIns +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 @@ -105,7 +107,6 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC // RTTI: - val isInterfaceSymbol = getInternalFunction("isInterface") val isArraySymbol = getInternalFunction("isArray") // val isCharSymbol = getInternalFunction("isChar") @@ -178,10 +179,15 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC val jsNumberRangeToLong = getInternalFunction("numberRangeToLong") val longClassSymbol = getInternalClassWithoutPackage("kotlin.Long") + val promiseClassSymbol: IrClassSymbol by context.lazy2 { getInternalClassWithoutPackage("kotlin.js.Promise") } + val metadataInterfaceConstructorSymbol = getInternalFunction("interfaceMeta") + val metadataObjectConstructorSymbol = getInternalFunction("objectMeta") + val metadataClassConstructorSymbol = getInternalFunction("classMeta") + val longToDouble = context.symbolTable.referenceSimpleFunction( context.getClass(FqName("kotlin.Long")).unsubstitutedMemberScope.findSingleFunction( Name.identifier("toDouble") diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt index 8a0ff734baf..01172468dfb 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/dce/JsUsefulDeclarationProcessor.kt @@ -8,6 +8,7 @@ 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.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 @@ -19,9 +20,7 @@ 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 +import org.jetbrains.kotlin.ir.util.* internal class JsUsefulDeclarationProcessor( override val context: JsIrBackendContext, @@ -112,6 +111,21 @@ internal class JsUsefulDeclarationProcessor( } } + 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") + else -> context.intrinsics.metadataClassConstructorSymbol.owner.enqueue(irClass, "class metadata") + } + } + } + + private fun IrClass.containsMetadata(): Boolean = + !isExternal && !isExpect && !isBuiltInClass(this) + override fun processConstructedClassDeclaration(declaration: IrDeclaration) { if (declaration in result) return diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MoveBodilessDeclarationsToSeparatePlace.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MoveBodilessDeclarationsToSeparatePlace.kt index cdc5e188f83..6b665a6d144 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MoveBodilessDeclarationsToSeparatePlace.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MoveBodilessDeclarationsToSeparatePlace.kt @@ -42,7 +42,7 @@ private val BODILESS_BUILTIN_CLASSES = listOf( "kotlin.Function" ).map { FqName(it) }.toSet() -private fun isBuiltInClass(declaration: IrDeclaration): Boolean = +fun isBuiltInClass(declaration: IrDeclaration): Boolean = declaration is IrClass && declaration.fqNameWhenAvailable in BODILESS_BUILTIN_CLASSES private val JsPackage = FqName("kotlin.js") diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt index 714990adadc..e7b094314c0 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsClassGenerator.kt @@ -51,6 +51,10 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo val jsClass = JsClass(name = className, baseClass = baseClassRef) + if (baseClass != null && !baseClass.isAny()) { + jsClass.baseClass = baseClassRef + } + if (es6mode) classModel.preDeclarationBlock.statements += jsClass.makeStmt() for (declaration in irClass.declarations) { @@ -337,81 +341,76 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo } private fun generateClassMetadata(): JsStatement { - val metadataLiteral = JsObjectLiteral(true) - val simpleName = irClass.name - - if (!simpleName.isSpecial) { - val simpleNameProp = JsPropertyInitializer(JsNameRef(Namer.METADATA_SIMPLE_NAME), JsStringLiteral(simpleName.identifier)) - metadataLiteral.propertyInitializers += simpleNameProp - } - - val classKind = JsStringLiteral( + val metadataConstructor = with(context.staticContext.backendContext.intrinsics) { when { - irClass.isInterface -> "interface" - irClass.isObject -> "object" - else -> "class" + irClass.isInterface -> metadataInterfaceConstructorSymbol + irClass.isObject -> metadataObjectConstructorSymbol + else -> metadataClassConstructorSymbol } - ) - metadataLiteral.propertyInitializers += JsPropertyInitializer(JsNameRef(Namer.METADATA_CLASS_KIND), classKind) - - metadataLiteral.propertyInitializers += generateSuperClasses() - - metadataLiteral.propertyInitializers += generateAssociatedKeyProperties() - - generateFastPrototype()?.let { metadataLiteral.propertyInitializers += it } - - if (isCoroutineClass()) { - metadataLiteral.propertyInitializers += generateSuspendArity() } - return jsAssignment(JsNameRef(Namer.METADATA, classNameRef), metadataLiteral).makeStmt() + 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() } private fun isCoroutineClass(): Boolean = irClass.superTypes.any { it.isSuspendFunctionTypeOrSubtype() } - private fun generateSuspendArity(): JsPropertyInitializer { + private fun generateSuspendArity(): JsArrayLiteral? { + if (!isCoroutineClass()) return null + val arity = context.staticContext.backendContext.mapping.suspendArityStore[irClass]!! .map { it.valueParameters.size } .distinct() .map { JsIntLiteral(it) } - return JsPropertyInitializer(JsNameRef(Namer.METADATA_SUSPEND_ARITY), JsArrayLiteral(arity)) + return JsArrayLiteral(arity) } - private fun generateSuperClasses(): JsPropertyInitializer { - return JsPropertyInitializer( - JsNameRef(Namer.METADATA_INTERFACES), - JsArrayLiteral( - 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 + 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) { - JsNameRef(context.getNameForClass(symbol.owner)) - } else null - } - ) - ) + 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 { - JsPropertyInitializer(JsNameRef(Namer.METADATA_FAST_PROTOTYPE), prototypeOf(it)) - } + private fun generateFastPrototype() = baseClassRef?.let { prototypeOf(it) } private fun IrType.isFunctionType() = isFunctionOrKFunction() || isSuspendFunctionOrKFunction() - private fun generateAssociatedKeyProperties(): List { - var result = emptyList() - - context.getAssociatedObjectKey(irClass)?.let { key -> - result = result + JsPropertyInitializer(JsStringLiteral("associatedObjectKey"), JsIntLiteral(key)) - } + private fun generateAssociatedObjectKey(): JsIntLiteral? { + return context.getAssociatedObjectKey(irClass)?.let { JsIntLiteral(it) } + } + private fun generateAssociatedObjects(): JsObjectLiteral? { val associatedObjects = irClass.annotations.mapNotNull { annotation -> val annotationClass = annotation.symbol.owner.constructedClass context.getAssociatedObjectKey(annotationClass)?.let { key -> @@ -423,11 +422,9 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo } } - if (associatedObjects.isNotEmpty()) { - result = result + JsPropertyInitializer(JsStringLiteral("associatedObjects"), JsObjectLiteral(associatedObjects)) - } - - return result + return associatedObjects + .takeIf { it.isNotEmpty() } + ?.let { JsObjectLiteral(it) } } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/Namer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/Namer.kt index 73937663a25..45deddf8111 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/Namer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/Namer.kt @@ -28,14 +28,11 @@ object Namer { val JS_ERROR = JsNameRef("Error") val JS_OBJECT = JsNameRef("Object") + val JS_UNDEFINED = JsNameRef("undefined") val JS_OBJECT_CREATE_FUNCTION = JsNameRef("create", JS_OBJECT) val METADATA = "\$metadata\$" - val METADATA_INTERFACES = "interfaces" - val METADATA_SIMPLE_NAME = "simpleName" - val METADATA_CLASS_KIND = "kind" - val METADATA_FAST_PROTOTYPE = "fastPrototype" - val METADATA_SUSPEND_ARITY = "suspendArity" + val METADATA_INTERFACE_ID = "interfaceId" val KCALLABLE_GET_NAME = "" val KCALLABLE_NAME = "callableName" diff --git a/libraries/stdlib/js-ir/runtime/reflectRuntime.kt b/libraries/stdlib/js-ir/runtime/reflectRuntime.kt index 5832af4bb62..7d03a8904d4 100644 --- a/libraries/stdlib/js-ir/runtime/reflectRuntime.kt +++ b/libraries/stdlib/js-ir/runtime/reflectRuntime.kt @@ -18,23 +18,34 @@ internal fun getLocalDelegateReference(name: String, type: dynamic, mutable: Boo return getPropertyCallableRef(name, 0, type, lambda, if (mutable) lambda else null) } -private fun getPropertyRefClass(obj: dynamic, metadata: dynamic): dynamic { +private fun getPropertyRefClass(obj: Ctor, metadata: Metadata): dynamic { obj.`$metadata$` = metadata; obj.constructor = obj; return obj; } private fun getKPropMetadata(paramCount: Int, setter: Any?, type: dynamic): dynamic { - val mdata = propertyRefClassMetadataCache[paramCount][if (setter == null) 0 else 1] - if (mdata.interfaces.length == 0) { - mdata.interfaces.push(type) + val mdata: Metadata = propertyRefClassMetadataCache[paramCount][if (setter == null) 0 else 1] + + if (mdata.interfaces.size == 0) { + mdata.interfaces.asDynamic().push(type) + + if (mdata.interfacesCache == null) { + mdata.interfacesCache = generateInterfaceCache() + } else { + mdata.interfacesCache!!.isComplete = false + } + + mdata.interfacesCache!!.extendCacheWithSingle(type) } return mdata } -@Suppress("NOTHING_TO_INLINE") -private inline fun metadataObject(): dynamic = js("{ kind: 'class', interfaces: [] }") +private fun metadataObject(): Metadata { + val undef = js("undefined") + return classMeta(undef, undef, undef, undef, undef, undef) +} private val propertyRefClassMetadataCache: Array> = arrayOf>( // immutable , mutable diff --git a/libraries/stdlib/js-ir/runtime/typeCheckUtils.kt b/libraries/stdlib/js-ir/runtime/typeCheckUtils.kt index 4a67a7efd89..fc572111ef3 100644 --- a/libraries/stdlib/js-ir/runtime/typeCheckUtils.kt +++ b/libraries/stdlib/js-ir/runtime/typeCheckUtils.kt @@ -5,62 +5,195 @@ package kotlin.js -private external interface Metadata { - val interfaces: Array - val suspendArity: Array? - - // This field gives fast access to the prototype of metadata owner (Object.getPrototypeOf()) - // Can be pre-initialized or lazy initialized and then should be immutable - var fastPrototype: Prototype? - - // The hint is used as a sort of flag, pointed to the class or the interface, which is not a parent for metadata owner - // Can be mutated quite often - var nonParentHint: dynamic +internal fun interfaceMeta( + name: String?, + interfaces: Array?, + associatedObjectKey: Number?, + associatedObjects: dynamic, + suspendArity: Array?, +): Metadata { + return createMetadata("interface", name, interfaces, associatedObjectKey, associatedObjects, suspendArity, js("undefined")) } -private external interface Ctor { - val `$metadata$`: Metadata? +internal fun objectMeta( + name: String?, + interfaces: Array?, + associatedObjectKey: Number?, + associatedObjects: dynamic, + suspendArity: Array?, + fastPrototype: Prototype?, +): Metadata { + return createMetadata("object", name, interfaces, associatedObjectKey, associatedObjects, suspendArity, fastPrototype) +} + +internal fun classMeta( + name: String?, + interfaces: Array?, + associatedObjectKey: Number?, + associatedObjects: dynamic, + suspendArity: Array?, + fastPrototype: Prototype?, +): Metadata { + return createMetadata("class", name, interfaces, associatedObjectKey, associatedObjects, suspendArity, fastPrototype) +} + +// Seems like we need to disable this check if variables are used inside js annotation +@Suppress("UNUSED_PARAMETER") +private fun createMetadata( + kind: String, + name: String?, + interfaces: Array?, + associatedObjectKey: Number?, + associatedObjects: dynamic, + suspendArity: Array?, + fastPrototype: Prototype? +): Metadata { + return js("""({ + kind: kind, + simpleName: name, + interfaceId: kind === "interface" ? -1 : undefined, + interfaces: interfaces || [], + associatedObjectKey: associatedObjectKey, + associatedObjects: associatedObjects, + suspendArity: suspendArity, + fastPrototype: fastPrototype, + ${'$'}kClass$: undefined, + interfacesCache: { + isComplete: fastPrototype === undefined && (interfaces === undefined || interfaces.length === 0), + implementInterfaceMemo: {} + } +})""") +} + +internal external interface Metadata { + val kind: String + val interfaces: Array + // This field gives fast access to the prototype of metadata owner (Object.getPrototypeOf()) + // Can be pre-initialized or lazy initialized and then should be immutable + val simpleName: String? + val associatedObjectKey: Number? + val associatedObjects: dynamic + var interfaceId: Number? + var fastPrototype: Prototype? + val suspendArity: Array? + + var `$kClass$`: dynamic + // This is an object for memoization of a isInterfaceImpl function + // Can be mutated quite often + var interfacesCache: IsImplementsCache? +} + +// This is a flag for memoization of a isInterfaceImpl function +internal external interface IsImplementsCache { + var isComplete: Boolean + val implementInterfaceMemo: dynamic +} + +internal external interface Ctor { + var `$metadata$`: Metadata? + var constructor: Ctor? val prototype: Prototype? } -private external interface Prototype { +internal external interface Prototype { val constructor: Ctor? } +private var interfacesCounter = 0 + +private fun Ctor.getOrDefineInterfaceId(): Number? { + val metadata = `$metadata$`.unsafeCast() + val interfaceId = metadata.interfaceId ?: -1 + return if (interfaceId != -1) { + interfaceId + } else { + val result = interfacesCounter++ + metadata.interfaceId = result + result + } +} + private fun Ctor.getPrototype() = prototype?.let { js("Object").getPrototypeOf(it).unsafeCast() } -private fun isInterfaceImpl(ctor: Ctor, iface: dynamic): Boolean { - if (ctor === iface) { - return true - } +internal fun IsImplementsCache.extendCacheWith(cache: IsImplementsCache?) { + val anotherInterfaceMemo = cache?.implementInterfaceMemo ?: return + js("Object").assign(implementInterfaceMemo, anotherInterfaceMemo) +} - val superPrototype = ctor.`$metadata$`?.run { - if (nonParentHint != null && nonParentHint === iface) { - return false - } - for (i in interfaces) { - if (isInterfaceImpl(i, iface)) { - return true - } - } +internal fun IsImplementsCache.extendCacheWithSingle(intr: Ctor) { + implementInterfaceMemo[intr.getOrDefineInterfaceId()] = true +} + +private fun fastGetPrototype(ctor: Ctor): Prototype? { + return ctor.`$metadata$`?.run { if (fastPrototype == null) { fastPrototype = ctor.getPrototype() } fastPrototype + } ?: ctor.getPrototype() +} + +private fun completeInterfaceCache(ctor: Ctor): IsImplementsCache? { + val metadata = ctor.`$metadata$` + + if (metadata != null && metadata.interfacesCache == null) { + metadata.interfacesCache = generateInterfaceCache() } - (superPrototype ?: ctor.getPrototype())?.constructor?.let { - if (isInterfaceImpl(it, iface)) { - return true + val interfacesCache = metadata?.interfacesCache + + if (interfacesCache != null) { + if (interfacesCache.isComplete == true) { + return interfacesCache + } + + for (i in metadata.interfaces) { + interfacesCache.extendCacheWithSingle(i) + interfacesCache.extendCacheWith(completeInterfaceCache(i)) } - ctor.`$metadata$`?.run { nonParentHint = iface } } - return false + + val constructor = fastGetPrototype(ctor)?.constructor ?: return null + val parentInterfacesCache = completeInterfaceCache(constructor) ?: return interfacesCache + + if (interfacesCache == null) return parentInterfacesCache + + interfacesCache.extendCacheWith(parentInterfacesCache) + interfacesCache.isComplete = true + + return interfacesCache +} + +// Old JS Backend +internal fun generateInterfaceCache(): IsImplementsCache { + return js("{ isComplete: false, implementInterfaceMemo: {} }") +} + +private fun isInterfaceImpl(ctor: Ctor, iface: Ctor): Boolean { + if (ctor === iface) { + return true + } + + val metadata = ctor.`$metadata$` + + if (metadata != null && metadata.interfacesCache == null) { + metadata.interfacesCache = generateInterfaceCache() + } + + val interfacesCache = metadata?.interfacesCache + + return if (interfacesCache != null) { + if (!interfacesCache.isComplete) completeInterfaceCache(ctor) + val interfaceId = iface.`$metadata$`?.interfaceId ?: return false + !!interfacesCache.implementInterfaceMemo[interfaceId] + } else { + val constructor = fastGetPrototype(ctor)?.constructor ?: return false + isInterfaceImpl(constructor, iface) + } } internal fun isInterface(obj: dynamic, iface: dynamic): Boolean { val ctor = obj.constructor ?: return false - return isInterfaceImpl(ctor, iface) } diff --git a/libraries/stdlib/js-ir/src/kotlin/reflection_js-ir.kt b/libraries/stdlib/js-ir/src/kotlin/reflection_js-ir.kt index cd15da6a0ed..0a489de3f41 100644 --- a/libraries/stdlib/js-ir/src/kotlin/reflection_js-ir.kt +++ b/libraries/stdlib/js-ir/src/kotlin/reflection_js-ir.kt @@ -9,8 +9,8 @@ import kotlin.reflect.js.internal.* @PublishedApi internal fun KClass<*>.findAssociatedObject(annotationClass: KClass): Any? { return if (this is KClassImpl<*> && annotationClass is KClassImpl) { - val key = annotationClass.jClass.asDynamic().`$metadata$`?.associatedObjectKey?.unsafeCast() ?: return null - val map = this.jClass.asDynamic().`$metadata$`?.associatedObjects ?: return null + val key = annotationClass.jClass.asDynamic().unsafeCast().`$metadata$`?.associatedObjectKey?.unsafeCast() ?: return null + val map = jClass.asDynamic().unsafeCast().`$metadata$`?.associatedObjects ?: return null val factory = map[key] ?: return null return factory() } else {