From 769fbcf3d08cf9181188cb63309e5e135c71e0fd Mon Sep 17 00:00:00 2001 From: Alexander Korepanov Date: Thu, 16 Dec 2021 18:07:00 +0300 Subject: [PATCH] [JS IR] Improve performance for runtime typecheck This is a part of KT-42743 --- .../transformers/irToJs/JsClassGenerator.kt | 20 ++++----- .../kotlin/ir/backend/js/utils/Namer.kt | 1 + .../stdlib/js-ir/runtime/typeCheckUtils.kt | 43 +++++++++++++++---- 3 files changed, 45 insertions(+), 19 deletions(-) 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 e9e9baeb833..714990adadc 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 @@ -31,7 +31,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo private val baseClass: IrType? = irClass.superTypes.firstOrNull { !it.classifierOrFail.isInterface } private val baseClassRef by lazy { // Lazy in case was not collected by namer during JsClassGenerator construction - baseClass?.getClassRef(context) + if (baseClass != null && !baseClass.isAny()) baseClass.getClassRef(context) else null } private val classPrototypeRef = prototypeOf(classNameRef) private val classBlock = JsGlobalBlock() @@ -49,11 +49,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo // We'll use IrSimpleFunction::correspondingProperty to collect them into set val properties = mutableSetOf() - val jsClass = JsClass(name = className) - - if (baseClass != null && !baseClass.isAny()) { - jsClass.baseClass = baseClassRef - } + val jsClass = JsClass(name = className, baseClass = baseClassRef) if (es6mode) classModel.preDeclarationBlock.statements += jsClass.makeStmt() @@ -329,12 +325,10 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo } private fun generateInheritanceCode(): List { - if (baseClass == null || baseClass.isAny()) { - return emptyList() - } + val baseClassPrototype = baseClassRef ?: return emptyList() val createCall = jsAssignment( - classPrototypeRef, JsInvocation(Namer.JS_OBJECT_CREATE_FUNCTION, prototypeOf(baseClassRef!!)) + classPrototypeRef, JsInvocation(Namer.JS_OBJECT_CREATE_FUNCTION, prototypeOf(baseClassPrototype)) ).makeStmt() val ctorAssign = jsAssignment(JsNameRef(Namer.CONSTRUCTOR_NAME, classPrototypeRef), classNameRef).makeStmt() @@ -364,6 +358,8 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo metadataLiteral.propertyInitializers += generateAssociatedKeyProperties() + generateFastPrototype()?.let { metadataLiteral.propertyInitializers += it } + if (isCoroutineClass()) { metadataLiteral.propertyInitializers += generateSuspendArity() } @@ -403,6 +399,10 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo ) } + private fun generateFastPrototype() = baseClassRef?.let { + JsPropertyInitializer(JsNameRef(Namer.METADATA_FAST_PROTOTYPE), prototypeOf(it)) + } + private fun IrType.isFunctionType() = isFunctionOrKFunction() || isSuspendFunctionOrKFunction() private fun generateAssociatedKeyProperties(): List { 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 7819a76393b..73937663a25 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 @@ -34,6 +34,7 @@ object Namer { 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 KCALLABLE_GET_NAME = "" diff --git a/libraries/stdlib/js-ir/runtime/typeCheckUtils.kt b/libraries/stdlib/js-ir/runtime/typeCheckUtils.kt index 9bdd6784041..493c51bc015 100644 --- a/libraries/stdlib/js-ir/runtime/typeCheckUtils.kt +++ b/libraries/stdlib/js-ir/runtime/typeCheckUtils.kt @@ -8,29 +8,54 @@ 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 } private external interface Ctor { val `$metadata$`: Metadata? - val prototype: Ctor? + val prototype: Prototype? } -private fun isInterfaceImpl(ctor: Ctor, iface: dynamic): Boolean { - if (ctor === iface) return true +private external interface Prototype { + val constructor: Ctor? +} - val metadata = ctor.`$metadata$` - if (metadata != null) { - val interfaces = metadata.interfaces +private fun Ctor.getPrototype() = prototype?.let { js("Object").getPrototypeOf(it).unsafeCast() } + +private fun isInterfaceImpl(ctor: Ctor, iface: dynamic): Boolean { + if (ctor === iface) { + return true + } + + val superPrototype = ctor.`$metadata$`?.run { + if (nonParentHint != null && nonParentHint === iface) { + return false + } for (i in interfaces) { if (isInterfaceImpl(i, iface)) { return true } } + if (fastPrototype == null) { + fastPrototype = ctor.getPrototype() + } + fastPrototype } - val superPrototype = if (ctor.prototype != null) js("Object").getPrototypeOf(ctor.prototype) else null - val superConstructor: Ctor? = if (superPrototype != null) superPrototype.constructor else null - return superConstructor != null && isInterfaceImpl(superConstructor, iface) + (superPrototype ?: ctor.getPrototype())?.constructor?.let { + if (isInterfaceImpl(it, iface)) { + return true + } + ctor.`$metadata$`?.run { nonParentHint = iface } + } + return false } internal fun isInterface(obj: dynamic, iface: dynamic): Boolean {