[JS IR] Introduce special setMetadataFor*() for synthetic classes

^KT-63436 Fixed
This commit is contained in:
Alexander Korepanov
2023-11-20 11:46:24 +01:00
committed by Space Team
parent 9af681e234
commit 45c166abf8
12 changed files with 240 additions and 106 deletions
@@ -112,7 +112,14 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
// RTTI:
val implementSymbol = getInternalFunction("implement")
val setMetadataForSymbol = getInternalFunction("setMetadataFor")
val initMetadataForSymbol = getInternalFunction("initMetadataFor")
val initMetadataForClassSymbol = getInternalFunction("initMetadataForClass")
val initMetadataForObjectSymbol = getInternalFunction("initMetadataForObject")
val initMetadataForInterfaceSymbol = getInternalFunction("initMetadataForInterface")
val initMetadataForLambdaSymbol = getInternalFunction("initMetadataForLambda")
val initMetadataForCoroutineSymbol = getInternalFunction("initMetadataForCoroutine")
val initMetadataForFunctionReferenceSymbol = getInternalFunction("initMetadataForFunctionReference")
val initMetadataForCompanionSymbol = getInternalFunction("initMetadataForCompanion")
val isInterfaceSymbol = getInternalFunction("isInterface")
val isArraySymbol = getInternalFunction("isArray")
@@ -198,10 +205,6 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
getInternalClassWithoutPackage("kotlin.js.Promise")
}
val metadataInterfaceConstructorSymbol = getInternalFunction("interfaceMeta")
val metadataObjectConstructorSymbol = getInternalFunction("objectMeta")
val metadataClassConstructorSymbol = getInternalFunction("classMeta")
val longToDouble = context.symbolTable.descriptorExtension.referenceSimpleFunction(
context.getClass(FqName("kotlin.Long")).unsubstitutedMemberScope.findSingleFunction(
Name.identifier("toDouble")
@@ -154,17 +154,25 @@ internal class JsUsefulDeclarationProcessor(
if (!irClass.containsMetadata()) return
when {
irClass.isObject -> context.intrinsics.metadataObjectConstructorSymbol.owner.enqueue(irClass, "object metadata")
irClass.isObject -> {
context.intrinsics.initMetadataForCompanionSymbol.owner.enqueue(irClass, "object metadata")
context.intrinsics.initMetadataForObjectSymbol.owner.enqueue(irClass, "companion object metadata")
}
irClass.isInterface -> {
context.intrinsics.implementSymbol.owner.enqueue(irClass, "interface metadata")
context.intrinsics.metadataInterfaceConstructorSymbol.owner.enqueue(irClass, "interface metadata")
context.intrinsics.initMetadataForInterfaceSymbol.owner.enqueue(irClass, "interface metadata")
}
else -> context.intrinsics.metadataClassConstructorSymbol.owner.enqueue(irClass, "class metadata")
else -> {
context.intrinsics.initMetadataForClassSymbol.owner.enqueue(irClass, "class metadata")
context.intrinsics.initMetadataForLambdaSymbol.owner.enqueue(irClass, "lambda metadata")
context.intrinsics.initMetadataForCoroutineSymbol.owner.enqueue(irClass, "coroutine metadata")
context.intrinsics.initMetadataForFunctionReferenceSymbol.owner.enqueue(irClass, "function reference metadata")
}
}
context.intrinsics.setMetadataForSymbol.owner.enqueue(irClass, "metadata")
context.intrinsics.initMetadataForSymbol.owner.enqueue(irClass, "metadata")
if (irClass.containsInterfaceDefaultImplementation()) {
context.intrinsics.jsPrototypeOfSymbol.owner.enqueue(irClass, "interface default implementation")
@@ -42,7 +42,7 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
private var IrFunction.coroutineConstructor by context.mapping.suspendFunctionToCoroutineConstructor
protected companion object {
companion object {
val DECLARATION_ORIGIN_COROUTINE_IMPL = IrDeclarationOriginImpl("COROUTINE_IMPL")
}
@@ -12,16 +12,20 @@ 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.lower.CallableReferenceLowering
import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.AbstractSuspendFunctionsLowering
import org.jetbrains.kotlin.ir.backend.js.lower.isEs6ConstructorReplacement
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
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.backend.ast.JsVars.JsVar
import org.jetbrains.kotlin.js.common.isValidES5Identifier
import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.butIf
import org.jetbrains.kotlin.utils.addToStdlib.runIf
@@ -255,7 +259,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
val metadataPlace = if (es6mode) classModel.postDeclarationBlock else classModel.preDeclarationBlock
metadataPlace.statements += generateSetMetadataCall()
metadataPlace.statements += generateInitMetadataCall()
context.staticContext.classModels[irClass.symbol] = classModel
return classBlock
@@ -371,39 +375,35 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
}
}
private fun generateSetMetadataCall(): JsStatement {
val setMetadataFor = backendContext.intrinsics.setMetadataForSymbol.owner
private fun generateInitMetadataCall(): JsStatement {
val ctor = classNameRef
val parent = baseClassRef?.takeIf { !es6mode }
val name = generateSimpleName()
val interfaces = generateInterfacesList()
val metadataConstructor = getMetadataConstructor()
val defaultConstructor = runIf(irClass.isClass, ::findDefaultConstructor)
val associatedObjectKey = generateAssociatedObjectKey()
val associatedObjects = generateAssociatedObjects()
val suspendArity = generateSuspendArity()
return JsInvocation(
JsNameRef(context.getNameForStaticFunction(setMetadataFor)),
listOf(
ctor,
name,
metadataConstructor,
parent,
interfaces,
defaultConstructor,
associatedObjectKey,
associatedObjects,
suspendArity
)
.dropLastWhile { it == null }
.memoryOptimizedMap { it ?: jsUndefined }
).makeStmt()
if (defaultConstructor == null && associatedObjectKey == null && associatedObjects == null) {
val initSpecialMetadata = getSpecialInitMetadata(name)
if (initSpecialMetadata != null) {
return initSpecialMetadata.invokeWithoutNullArgs(ctor, parent, interfaces, suspendArity)
}
}
return getInitMetadata().invokeWithoutNullArgs(
ctor,
name,
defaultConstructor,
parent,
interfaces,
suspendArity,
associatedObjectKey,
associatedObjects
)
}
private fun IrType.asConstructorRef(): JsExpression? {
val ownerSymbol = classOrNull?.takeIf {
!isAny() && !isFunctionType() && !it.owner.isEffectivelyExternal()
@@ -418,16 +418,41 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
return irClass.name.takeIf { !it.isSpecial }?.let { JsStringLiteral(it.identifier) }
}
private fun getMetadataConstructor(): JsNameRef {
val metadataConstructorSymbol = with(backendContext.intrinsics) {
private fun getInitMetadata(): IrSimpleFunctionSymbol {
return with(backendContext.intrinsics) {
when {
irClass.isInterface -> metadataInterfaceConstructorSymbol
irClass.isObject -> metadataObjectConstructorSymbol
else -> metadataClassConstructorSymbol
irClass.isInterface -> initMetadataForInterfaceSymbol
irClass.isObject -> initMetadataForObjectSymbol
else -> initMetadataForClassSymbol
}
}
}
return JsNameRef(context.getNameForStaticFunction(metadataConstructorSymbol.owner))
private fun getSpecialInitMetadata(name: JsStringLiteral?): IrSimpleFunctionSymbol? {
if (irClass.isCompanion && name?.value == SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT.asString()) {
return backendContext.intrinsics.initMetadataForCompanionSymbol
}
if (irClass.isClass) {
when (irClass.origin) {
CallableReferenceLowering.LAMBDA_IMPL -> {
return backendContext.intrinsics.initMetadataForLambdaSymbol
}
CallableReferenceLowering.FUNCTION_REFERENCE_IMPL -> {
return backendContext.intrinsics.initMetadataForFunctionReferenceSymbol
}
AbstractSuspendFunctionsLowering.DECLARATION_ORIGIN_COROUTINE_IMPL -> {
return backendContext.intrinsics.initMetadataForCoroutineSymbol
}
}
}
return null
}
private fun IrSimpleFunctionSymbol.invokeWithoutNullArgs(vararg arguments: JsExpression?): JsStatement {
return JsInvocation(
JsNameRef(context.getNameForStaticFunction(owner)),
arguments.dropLastWhile { it == null }.memoryOptimizedMap { it ?: jsUndefined }
).makeStmt()
}
private fun generateInterfacesList(): JsArrayLiteral? {
@@ -36,8 +36,8 @@ suspend fun box() {
// test.kt:14 box: $completion=EmptyContinuation
// test.kt:14 box: $completion=EmptyContinuation
// test.kt:7 doResume:
// test.kt:5 foo: $completion=$foo1COROUTINE$0
// test.kt:5 foo: $completion=Coroutine
// test.kt:8 doResume:
// test.kt:5 foo: $completion=$foo1COROUTINE$0
// test.kt:5 foo: $completion=Coroutine
// test.kt:9 doResume:
// test.kt:10 doResume: dead=kotlin.Long
@@ -30,8 +30,8 @@ suspend fun box() {
// test.kt:13 box: $completion=EmptyContinuation
// test.kt:12 box: $completion=EmptyContinuation
// test.kt:6 doResume:
// test.kt:4 foo: $completion=$foo1COROUTINE$0
// test.kt:4 foo: $completion=Coroutine
// test.kt:7 doResume:
// test.kt:4 foo: $completion=$foo1COROUTINE$0
// test.kt:4 foo: $completion=Coroutine
// test.kt:8 doResume:
// test.kt:9 doResume: dead=kotlin.Long
@@ -38,8 +38,8 @@ suspend fun box() {
// test.kt:14 box: $completion=EmptyContinuation
// test.kt:14 box: $completion=EmptyContinuation
// test.kt:8 doResume:
// test.kt:6 foo: <this>=A, $completion=$foo1COROUTINE$0
// test.kt:6 foo: <this>=A, $completion=Coroutine
// test.kt:9 doResume:
// test.kt:6 foo: <this>=A, $completion=$foo1COROUTINE$0
// test.kt:6 foo: <this>=A, $completion=Coroutine
// test.kt:10 doResume:
// test.kt:11 doResume: dead=kotlin.Long
@@ -34,7 +34,7 @@ suspend fun box() {
// EXPECTATIONS JS_IR
// test.kt:12 doResume:
// test.kt:8 h: $completion=$boxCOROUTINE$0
// test.kt:8 h: $completion=Coroutine
// test.kt:4 doResume:
// test.kt:4 doResume: x=41:number
// test.kt:19 doResume: x=41:number, x=41:number
@@ -48,7 +48,7 @@ suspend fun box() {
// EXPECTATIONS JS_IR
// test.kt:12 doResume:
// test.kt:6 h: $completion=$boxCOROUTINE$0
// test.kt:6 h: $completion=Coroutine
// test.kt:13 doResume:
// test.kt:13 doResume:
// test.kt:13 doResume:
@@ -0,0 +1,151 @@
/*
* Copyright 2010-2023 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 kotlin.js
// There was a problem with per-module compilation (KT-55758) when the top-level state (globalInterfaceId) was reinitialized during stdlib module initialization
// As a result we miss already incremented globalInterfaceId and had the same interfaceIds in two different modules
// So, to keep the state consistent it was moved into the variable without initializer and function
@Suppress("MUST_BE_INITIALIZED")
private var globalInterfaceId: dynamic
private fun generateInterfaceId(): Int {
if (globalInterfaceId === VOID) {
globalInterfaceId = 0
}
globalInterfaceId = globalInterfaceId.unsafeCast<Int>() + 1
return globalInterfaceId.unsafeCast<Int>()
}
internal const val METADATA_KIND_INTERFACE = "interface"
internal const val METADATA_KIND_OBJECT = "object"
internal const val METADATA_KIND_CLASS = "class"
internal fun initMetadataFor(
kind: String,
ctor: Ctor,
name: String?,
defaultConstructor: dynamic,
parent: Ctor?,
interfaces: Array<dynamic>?,
suspendArity: Array<Int>?,
associatedObjectKey: Number?,
associatedObjects: dynamic
) {
if (parent != null) {
js("""
ctor.prototype = Object.create(parent.prototype)
ctor.prototype.constructor = ctor;
""")
}
val metadata = createMetadata(kind, name, defaultConstructor, associatedObjectKey, associatedObjects, suspendArity)
ctor.`$metadata$` = metadata
if (interfaces != null) {
val receiver = if (metadata.iid != VOID) ctor else ctor.prototype
receiver.`$imask$` = implement(interfaces)
}
}
internal fun initMetadataForClass(
ctor: Ctor,
name: String?,
defaultConstructor: dynamic,
parent: Ctor?,
interfaces: Array<dynamic>?,
suspendArity: Array<Int>?,
associatedObjectKey: Number?,
associatedObjects: dynamic
) {
val kind = METADATA_KIND_CLASS
initMetadataFor(kind, ctor, name, defaultConstructor, parent, interfaces, suspendArity, associatedObjectKey, associatedObjects)
}
internal fun initMetadataForObject(
ctor: Ctor,
name: String?,
defaultConstructor: dynamic,
parent: Ctor?,
interfaces: Array<dynamic>?,
suspendArity: Array<Int>?,
associatedObjectKey: Number?,
associatedObjects: dynamic
) {
val kind = METADATA_KIND_OBJECT
initMetadataFor(kind, ctor, name, defaultConstructor, parent, interfaces, suspendArity, associatedObjectKey, associatedObjects)
}
internal fun initMetadataForInterface(
ctor: Ctor,
name: String?,
defaultConstructor: dynamic,
parent: Ctor?,
interfaces: Array<dynamic>?,
suspendArity: Array<Int>?,
associatedObjectKey: Number?,
associatedObjects: dynamic
) {
val kind = METADATA_KIND_INTERFACE
initMetadataFor(kind, ctor, name, defaultConstructor, parent, interfaces, suspendArity, associatedObjectKey, associatedObjects)
}
internal fun initMetadataForLambda(ctor: Ctor, parent: Ctor?, interfaces: Array<dynamic>?, suspendArity: Array<Int>?) {
initMetadataForClass(ctor, "Lambda", VOID, parent, interfaces, suspendArity, VOID, VOID)
}
internal fun initMetadataForFunctionReference(ctor: Ctor, parent: Ctor?, interfaces: Array<dynamic>?, suspendArity: Array<Int>?) {
initMetadataForClass(ctor, "FunctionReference", VOID, parent, interfaces, suspendArity, VOID, VOID)
}
internal fun initMetadataForCoroutine(ctor: Ctor, parent: Ctor?, interfaces: Array<dynamic>?, suspendArity: Array<Int>?) {
initMetadataForClass(ctor, "Coroutine", VOID, parent, interfaces, suspendArity, VOID, VOID)
}
internal fun initMetadataForCompanion(ctor: Ctor, parent: Ctor?, interfaces: Array<dynamic>?, suspendArity: Array<Int>?) {
initMetadataForObject(ctor, "Companion", VOID, parent, interfaces, suspendArity, VOID, VOID)
}
// Seems like we need to disable this check if variables are used inside js annotation
@Suppress("UNUSED_PARAMETER", "UNUSED_VARIABLE")
internal fun createMetadata(
kind: String,
name: String?,
defaultConstructor: dynamic,
associatedObjectKey: Number?,
associatedObjects: dynamic,
suspendArity: Array<Int>?,
): Metadata {
val undef = VOID
val iid = if (kind == METADATA_KIND_INTERFACE) generateInterfaceId() else VOID
return js("""({
kind: kind,
simpleName: name,
associatedObjectKey: associatedObjectKey,
associatedObjects: associatedObjects,
suspendArity: suspendArity,
${'$'}kClass$: undef,
defaultConstructor: defaultConstructor,
iid: iid
})""")
}
internal external interface Metadata {
// TODO: This field can be used from user libs, e.g. kotlinx.serialization:
// https://github.com/Kotlin/kotlinx.serialization/blob/b8de86f0e351f1099d2afb03ff92e2ef6256cbc7/core/jsMain/src/kotlinx/serialization/internal/Platform.kt#L74
// This must be reworked
val kind: String
// 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
val suspendArity: Array<Int>?
val iid: Int?
var `$kClass$`: dynamic
val defaultConstructor: dynamic
var errorInfo: Int? // Bits set for overridden properties: "message" => 0x1, "cause" => 0x2
}
@@ -4,6 +4,8 @@
*/
package kotlin.js
// TODO: Remove this file after bootstrap
internal fun setMetadataFor(
ctor: Ctor,
name: String?,
@@ -31,21 +33,6 @@ internal fun setMetadataFor(
}
}
// There was a problem with per-module compilation (KT-55758) when the top-level state (iid) was reinitialized during stdlib module initialization
// As a result we miss already incremented iid and had the same iids in two different modules
// So, to keep the state consistent it was moved into the variable without initializer and function
@Suppress("MUST_BE_INITIALIZED")
private var iid: dynamic
private fun generateInterfaceId(): Int {
if (iid === VOID) {
iid = 0
}
iid = iid.unsafeCast<Int>() + 1
return iid.unsafeCast<Int>()
}
internal fun interfaceMeta(
name: String?,
defaultConstructor: dynamic,
@@ -53,7 +40,7 @@ internal fun interfaceMeta(
associatedObjects: dynamic,
suspendArity: Array<Int>?
): Metadata {
return createMetadata("interface", name, defaultConstructor, associatedObjectKey, associatedObjects, suspendArity, generateInterfaceId())
return createMetadata(METADATA_KIND_INTERFACE, name, defaultConstructor, associatedObjectKey, associatedObjects, suspendArity)
}
internal fun objectMeta(
@@ -63,7 +50,7 @@ internal fun objectMeta(
associatedObjects: dynamic,
suspendArity: Array<Int>?
): Metadata {
return createMetadata("object", name, defaultConstructor, associatedObjectKey, associatedObjects, suspendArity, null)
return createMetadata(METADATA_KIND_OBJECT, name, defaultConstructor, associatedObjectKey, associatedObjects, suspendArity)
}
internal fun classMeta(
@@ -73,45 +60,5 @@ internal fun classMeta(
associatedObjects: dynamic,
suspendArity: Array<Int>?
): Metadata {
return createMetadata("class", name, defaultConstructor, associatedObjectKey, associatedObjects, suspendArity, null)
return createMetadata(METADATA_KIND_CLASS, name, defaultConstructor, associatedObjectKey, associatedObjects, suspendArity)
}
// Seems like we need to disable this check if variables are used inside js annotation
@Suppress("UNUSED_PARAMETER", "UNUSED_VARIABLE")
private fun createMetadata(
kind: String,
name: String?,
defaultConstructor: dynamic,
associatedObjectKey: Number?,
associatedObjects: dynamic,
suspendArity: Array<Int>?,
iid: Int?
): Metadata {
val undef = VOID
return js("""({
kind: kind,
simpleName: name,
associatedObjectKey: associatedObjectKey,
associatedObjects: associatedObjects,
suspendArity: suspendArity,
${'$'}kClass$: undef,
defaultConstructor: defaultConstructor,
iid: iid
})""")
}
internal external interface Metadata {
val kind: String
// 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
val suspendArity: Array<Int>?
val iid: Int?
var `$kClass$`: dynamic
val defaultConstructor: dynamic
var errorInfo: Int? // Bits set for overridden properties: "message" => 0x1, "cause" => 0x2
}
@@ -114,7 +114,7 @@ internal fun jsIsType(obj: dynamic, jsClass: dynamic): Boolean {
val constructor = if (jsClassType == "object") jsGetPrototypeOf(jsClass) else jsClass
val klassMetadata = constructor.`$metadata$`
if (klassMetadata?.kind === "interface") {
if (klassMetadata?.kind === METADATA_KIND_INTERFACE) {
val iid = klassMetadata.iid.unsafeCast<Int?>() ?: return false
return isInterfaceImpl(obj, iid)
}
@@ -141,4 +141,4 @@ internal fun isCharSequence(value: dynamic): Boolean =
@OptIn(JsIntrinsic::class)
internal fun isExternalObject(value: dynamic, ktExternalObject: dynamic) =
jsEqeqeq(value, ktExternalObject) || (jsTypeOf(ktExternalObject) == "function" && jsInstanceOf(value, ktExternalObject))
jsEqeqeq(value, ktExternalObject) || (jsTypeOf(ktExternalObject) == "function" && jsInstanceOf(value, ktExternalObject))