[Wasm] Support lazy associated object initialisation
Fix #KT-63939
This commit is contained in:
committed by
Space Team
parent
e7c1de1356
commit
552ee1ee38
@@ -77,8 +77,7 @@ class WasmSymbols(
|
|||||||
getProperty(FqName.fromSegments(listOf("kotlin", "wasm", "internal", "isNotFirstWasmExportCall")))
|
getProperty(FqName.fromSegments(listOf("kotlin", "wasm", "internal", "isNotFirstWasmExportCall")))
|
||||||
)
|
)
|
||||||
|
|
||||||
internal val initAssociatedObjects = getInternalFunction("initAssociatedObjects")
|
internal val tryGetAssociatedObject = getInternalFunction("tryGetAssociatedObject")
|
||||||
internal val addAssociatedObject = getInternalFunction("addAssociatedObject")
|
|
||||||
|
|
||||||
override val throwNullPointerException = getInternalFunction("THROW_NPE")
|
override val throwNullPointerException = getInternalFunction("THROW_NPE")
|
||||||
override val throwISE = getInternalFunction("THROW_ISE")
|
override val throwISE = getInternalFunction("THROW_ISE")
|
||||||
|
|||||||
+24
-28
@@ -7,11 +7,12 @@ package org.jetbrains.kotlin.backend.wasm.lower
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||||
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||||
|
import org.jetbrains.kotlin.backend.common.lower.irIfThen
|
||||||
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
|
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
|
||||||
import org.jetbrains.kotlin.backend.wasm.WasmSymbols
|
import org.jetbrains.kotlin.backend.wasm.WasmSymbols
|
||||||
import org.jetbrains.kotlin.ir.IrBuiltIns
|
import org.jetbrains.kotlin.ir.IrBuiltIns
|
||||||
import org.jetbrains.kotlin.ir.IrElement
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
import org.jetbrains.kotlin.ir.IrStatement
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.associatedObject
|
import org.jetbrains.kotlin.ir.backend.js.utils.associatedObject
|
||||||
import org.jetbrains.kotlin.ir.builders.*
|
import org.jetbrains.kotlin.ir.builders.*
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||||
@@ -45,7 +46,7 @@ class AssociatedObjectsLowering(val context: WasmBackendContext) : FileLoweringP
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val visitor = object : IrElementVisitorVoid {
|
private val visitor = object : IrElementVisitorVoid {
|
||||||
val initFunctionStatements = (context.wasmSymbols.initAssociatedObjects.owner.body as IrBlockBody).statements
|
val tryGetAssociatedObject = (context.wasmSymbols.tryGetAssociatedObject.owner.body as IrBlockBody).statements
|
||||||
|
|
||||||
override fun visitElement(element: IrElement) {
|
override fun visitElement(element: IrElement) {
|
||||||
if (element is IrClass) {
|
if (element is IrClass) {
|
||||||
@@ -62,49 +63,44 @@ class AssociatedObjectsLowering(val context: WasmBackendContext) : FileLoweringP
|
|||||||
if (klassAnnotation.valueArgumentsCount != 1) continue
|
if (klassAnnotation.valueArgumentsCount != 1) continue
|
||||||
val associatedObject = klassAnnotation.associatedObject() ?: continue
|
val associatedObject = klassAnnotation.associatedObject() ?: continue
|
||||||
|
|
||||||
val builder = cachedBuilder ?: context.createIrBuilder(context.wasmSymbols.initAssociatedObjects)
|
val builder = cachedBuilder ?: context.createIrBuilder(context.wasmSymbols.tryGetAssociatedObject)
|
||||||
cachedBuilder = builder
|
cachedBuilder = builder
|
||||||
|
|
||||||
val addCall = builder.createAssociatedObjectAdd(
|
val selector = builder.createAssociatedObjectSelector(
|
||||||
wasmSymbols = context.wasmSymbols,
|
wasmSymbols = context.wasmSymbols,
|
||||||
irBuiltIns = context.irBuiltIns,
|
irBuiltIns = context.irBuiltIns,
|
||||||
targetClass = declaration.symbol,
|
targetClass = declaration.symbol,
|
||||||
keyAnnotation = annotationClass.symbol,
|
keyAnnotation = annotationClass.symbol,
|
||||||
associatedObject = associatedObject.symbol
|
associatedObject = associatedObject.symbol
|
||||||
)
|
)
|
||||||
initFunctionStatements.add(addCall)
|
tryGetAssociatedObject.add(0, selector)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun IrBuilderWithScope.createAssociatedObjectAdd(
|
private fun IrBuilderWithScope.createAssociatedObjectSelector(
|
||||||
wasmSymbols: WasmSymbols,
|
wasmSymbols: WasmSymbols,
|
||||||
irBuiltIns: IrBuiltIns,
|
irBuiltIns: IrBuiltIns,
|
||||||
targetClass: IrClassSymbol,
|
targetClass: IrClassSymbol,
|
||||||
keyAnnotation: IrClassSymbol,
|
keyAnnotation: IrClassSymbol,
|
||||||
associatedObject: IrClassSymbol
|
associatedObject: IrClassSymbol
|
||||||
): IrCall = buildStatement(UNDEFINED_OFFSET, UNDEFINED_OFFSET) {
|
): IrStatement {
|
||||||
irCall(wasmSymbols.addAssociatedObject, irBuiltIns.unitType).also { addCall ->
|
val classIdParam = irGet(wasmSymbols.tryGetAssociatedObject.owner.valueParameters[0])
|
||||||
addCall.putValueArgument(
|
val keyIdParam = irGet(wasmSymbols.tryGetAssociatedObject.owner.valueParameters[1])
|
||||||
0,
|
|
||||||
irGet(wasmSymbols.initAssociatedObjects.owner.valueParameters[0])
|
val classId = irCall(wasmSymbols.wasmTypeId, irBuiltIns.intType).also {
|
||||||
)
|
it.putTypeArgument(0, targetClass.defaultType)
|
||||||
addCall.putValueArgument(
|
|
||||||
1,
|
|
||||||
irCall(wasmSymbols.wasmTypeId, irBuiltIns.intType).also {
|
|
||||||
it.putTypeArgument(0, targetClass.defaultType)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
addCall.putValueArgument(
|
|
||||||
2,
|
|
||||||
irCall(wasmSymbols.wasmTypeId, irBuiltIns.intType).also {
|
|
||||||
it.putTypeArgument(0, keyAnnotation.defaultType)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
addCall.putValueArgument(
|
|
||||||
3,
|
|
||||||
irGetObjectValue(irBuiltIns.anyType, associatedObject)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
val keyId = irCall(wasmSymbols.wasmTypeId, irBuiltIns.intType).also {
|
||||||
|
it.putTypeArgument(0, keyAnnotation.defaultType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return irIfThen(
|
||||||
|
condition = irEquals(classIdParam, classId),
|
||||||
|
thenPart = irIfThen(
|
||||||
|
condition = irEquals(keyIdParam, keyId),
|
||||||
|
thenPart = irReturn(irGetObjectValue(irBuiltIns.anyType, associatedObject))
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
+6
@@ -10314,6 +10314,12 @@ public class FirJsES6BoxTestGenerated extends AbstractFirJsES6BoxTest {
|
|||||||
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectInSeparatedFile.kt");
|
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectInSeparatedFile.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("findAssociatedObjectLazyness.kt")
|
||||||
|
public void testFindAssociatedObjectLazyness() throws Exception {
|
||||||
|
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectLazyness.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("findAssociatedObject_oldBE.kt")
|
@TestMetadata("findAssociatedObject_oldBE.kt")
|
||||||
public void testFindAssociatedObject_oldBE() throws Exception {
|
public void testFindAssociatedObject_oldBE() throws Exception {
|
||||||
|
|||||||
Generated
+6
@@ -10208,6 +10208,12 @@ public class FirLightTreeJsBoxTestGenerated extends AbstractFirLightTreeJsBoxTes
|
|||||||
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectInSeparatedFile.kt");
|
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectInSeparatedFile.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("findAssociatedObjectLazyness.kt")
|
||||||
|
public void testFindAssociatedObjectLazyness() throws Exception {
|
||||||
|
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectLazyness.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("findAssociatedObject_oldBE.kt")
|
@TestMetadata("findAssociatedObject_oldBE.kt")
|
||||||
public void testFindAssociatedObject_oldBE() throws Exception {
|
public void testFindAssociatedObject_oldBE() throws Exception {
|
||||||
|
|||||||
+6
@@ -10208,6 +10208,12 @@ public class FirPsiJsBoxTestGenerated extends AbstractFirPsiJsBoxTest {
|
|||||||
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectInSeparatedFile.kt");
|
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectInSeparatedFile.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("findAssociatedObjectLazyness.kt")
|
||||||
|
public void testFindAssociatedObjectLazyness() throws Exception {
|
||||||
|
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectLazyness.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("findAssociatedObject_oldBE.kt")
|
@TestMetadata("findAssociatedObject_oldBE.kt")
|
||||||
public void testFindAssociatedObject_oldBE() throws Exception {
|
public void testFindAssociatedObject_oldBE() throws Exception {
|
||||||
|
|||||||
+6
@@ -10314,6 +10314,12 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test {
|
|||||||
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectInSeparatedFile.kt");
|
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectInSeparatedFile.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("findAssociatedObjectLazyness.kt")
|
||||||
|
public void testFindAssociatedObjectLazyness() throws Exception {
|
||||||
|
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectLazyness.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("findAssociatedObject_oldBE.kt")
|
@TestMetadata("findAssociatedObject_oldBE.kt")
|
||||||
public void testFindAssociatedObject_oldBE() throws Exception {
|
public void testFindAssociatedObject_oldBE() throws Exception {
|
||||||
|
|||||||
+6
@@ -10208,6 +10208,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
|
|||||||
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectInSeparatedFile.kt");
|
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectInSeparatedFile.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("findAssociatedObjectLazyness.kt")
|
||||||
|
public void testFindAssociatedObjectLazyness() throws Exception {
|
||||||
|
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectLazyness.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("findAssociatedObject_oldBE.kt")
|
@TestMetadata("findAssociatedObject_oldBE.kt")
|
||||||
public void testFindAssociatedObject_oldBE() throws Exception {
|
public void testFindAssociatedObject_oldBE() throws Exception {
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
@file:OptIn(ExperimentalAssociatedObjects::class)
|
||||||
|
|
||||||
|
import kotlin.reflect.AssociatedObjectKey
|
||||||
|
import kotlin.reflect.ExperimentalAssociatedObjects
|
||||||
|
import kotlin.reflect.findAssociatedObject
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
private var obj1Init = false
|
||||||
|
private var obj2Init = false
|
||||||
|
private var obj3Init = false
|
||||||
|
|
||||||
|
@AssociatedObjectKey
|
||||||
|
annotation class KEY1(val kClass: KClass<*>)
|
||||||
|
|
||||||
|
@AssociatedObjectKey
|
||||||
|
annotation class KEY2(val kClass: KClass<*>)
|
||||||
|
|
||||||
|
private object OBJ1 {
|
||||||
|
init { obj1Init = true }
|
||||||
|
}
|
||||||
|
|
||||||
|
private object OBJ2 {
|
||||||
|
init { obj2Init = true }
|
||||||
|
}
|
||||||
|
|
||||||
|
private object OBJ3 {
|
||||||
|
init { obj3Init = true }
|
||||||
|
}
|
||||||
|
|
||||||
|
@KEY1(OBJ1::class)
|
||||||
|
class CLS1
|
||||||
|
|
||||||
|
@KEY1(OBJ2::class)
|
||||||
|
@KEY2(OBJ3::class)
|
||||||
|
class CLS2
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
// No objects initialised
|
||||||
|
if (obj1Init) return "FAIL1"
|
||||||
|
if (obj2Init) return "FAIL2"
|
||||||
|
if (obj3Init) return "FAIL3"
|
||||||
|
|
||||||
|
CLS1::class.findAssociatedObject<KEY2>()
|
||||||
|
if (obj1Init) return "FAIL4"
|
||||||
|
if (obj2Init) return "FAIL5"
|
||||||
|
if (obj3Init) return "FAIL6"
|
||||||
|
|
||||||
|
CLS1::class.findAssociatedObject<KEY1>()
|
||||||
|
if (!obj1Init) return "FAIL7"
|
||||||
|
if (obj2Init) return "FAIL8"
|
||||||
|
if (obj3Init) return "FAIL9"
|
||||||
|
|
||||||
|
CLS2::class.findAssociatedObject<KEY1>()
|
||||||
|
if (!obj1Init) return "FAIL10"
|
||||||
|
if (!obj2Init) return "FAIL11"
|
||||||
|
if (obj3Init) return "FAIL12"
|
||||||
|
|
||||||
|
CLS2::class.findAssociatedObject<KEY2>()
|
||||||
|
if (!obj1Init) return "FAIL13"
|
||||||
|
if (!obj2Init) return "FAIL14"
|
||||||
|
if (!obj3Init) return "FAIL15"
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
@@ -8,35 +8,16 @@ package kotlin.wasm.internal
|
|||||||
import kotlin.reflect.wasm.internal.KClassImpl
|
import kotlin.reflect.wasm.internal.KClassImpl
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
internal var associatedObjects: Map<ULong, Any>? = null
|
|
||||||
|
|
||||||
@PublishedApi
|
@PublishedApi
|
||||||
internal fun findAssociatedObject(klass: KClass<*>, key: Int): Any? {
|
internal fun findAssociatedObject(klass: KClass<*>, key: Int): Any? {
|
||||||
val klassId = (klass as? KClassImpl<*>)?.typeData?.typeId ?: return null
|
val klassId = (klass as? KClassImpl<*>)?.typeData?.typeId ?: return null
|
||||||
|
return tryGetAssociatedObject(klassId, key)
|
||||||
val map = associatedObjects ?: run {
|
|
||||||
val newMap: MutableMap<ULong, Any> = mutableMapOf()
|
|
||||||
initAssociatedObjects(newMap)
|
|
||||||
associatedObjects = newMap
|
|
||||||
newMap
|
|
||||||
}
|
|
||||||
|
|
||||||
return map[packIntoULong(klassId, key)]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun packIntoULong(a: Int, b: Int): ULong =
|
internal fun tryGetAssociatedObject(
|
||||||
(a.toUInt().toULong() shl Int.SIZE_BITS) or b.toUInt().toULong()
|
@Suppress("UNUSED_PARAMETER") klassId: Int,
|
||||||
|
@Suppress("UNUSED_PARAMETER") keyId: Int,
|
||||||
internal fun addAssociatedObject(
|
): Any? {
|
||||||
mapToInit: MutableMap<ULong, Any>,
|
|
||||||
klassId: Int,
|
|
||||||
keyId: Int,
|
|
||||||
instance: Any
|
|
||||||
) {
|
|
||||||
mapToInit[packIntoULong(klassId, keyId)] = instance
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun initAssociatedObjects(@Suppress("UNUSED_PARAMETER") mapToInit: MutableMap<ULong, Any>) {
|
|
||||||
// Init implicitly with AssociatedObjectsLowering
|
// Init implicitly with AssociatedObjectsLowering
|
||||||
// addAssociatedObject(mapToInit, ...)
|
return null
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ fun main(args: Array<String>) {
|
|||||||
val k2BoxTestDir = "multiplatform/k2"
|
val k2BoxTestDir = "multiplatform/k2"
|
||||||
|
|
||||||
val jsTranslatorTestPattern = "^([^_](.+))\\.kt$"
|
val jsTranslatorTestPattern = "^([^_](.+))\\.kt$"
|
||||||
val jsTranslatorReflectionPattern = "^(findAssociatedObject(InSeparatedFile)?)\\.kt$"
|
val jsTranslatorReflectionPattern = "^(findAssociatedObject(InSeparatedFile)?(Lazyness)?)\\.kt$"
|
||||||
val jsTranslatorEsModulesExcludedDirs = listOf(
|
val jsTranslatorEsModulesExcludedDirs = listOf(
|
||||||
// JsExport is not supported for classes
|
// JsExport is not supported for classes
|
||||||
"jsExport", "native", "export",
|
"jsExport", "native", "export",
|
||||||
|
|||||||
Generated
+7
-1
@@ -514,7 +514,7 @@ public class FirWasmJsTranslatorTestGenerated extends AbstractFirWasmJsTranslato
|
|||||||
public class Reflection {
|
public class Reflection {
|
||||||
@Test
|
@Test
|
||||||
public void testAllFilesPresentInReflection() throws Exception {
|
public void testAllFilesPresentInReflection() throws Exception {
|
||||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/reflection"), Pattern.compile("^(findAssociatedObject(InSeparatedFile)?)\\.kt$"), null, TargetBackend.WASM, true);
|
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/reflection"), Pattern.compile("^(findAssociatedObject(InSeparatedFile)?(Lazyness)?)\\.kt$"), null, TargetBackend.WASM, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -528,6 +528,12 @@ public class FirWasmJsTranslatorTestGenerated extends AbstractFirWasmJsTranslato
|
|||||||
public void testFindAssociatedObjectInSeparatedFile() throws Exception {
|
public void testFindAssociatedObjectInSeparatedFile() throws Exception {
|
||||||
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectInSeparatedFile.kt");
|
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectInSeparatedFile.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("findAssociatedObjectLazyness.kt")
|
||||||
|
public void testFindAssociatedObjectLazyness() throws Exception {
|
||||||
|
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectLazyness.kt");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
|
|||||||
Generated
+7
-1
@@ -514,7 +514,7 @@ public class K1WasmJsTranslatorTestGenerated extends AbstractK1WasmJsTranslatorT
|
|||||||
public class Reflection {
|
public class Reflection {
|
||||||
@Test
|
@Test
|
||||||
public void testAllFilesPresentInReflection() throws Exception {
|
public void testAllFilesPresentInReflection() throws Exception {
|
||||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/reflection"), Pattern.compile("^(findAssociatedObject(InSeparatedFile)?)\\.kt$"), null, TargetBackend.WASM, true);
|
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/reflection"), Pattern.compile("^(findAssociatedObject(InSeparatedFile)?(Lazyness)?)\\.kt$"), null, TargetBackend.WASM, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -528,6 +528,12 @@ public class K1WasmJsTranslatorTestGenerated extends AbstractK1WasmJsTranslatorT
|
|||||||
public void testFindAssociatedObjectInSeparatedFile() throws Exception {
|
public void testFindAssociatedObjectInSeparatedFile() throws Exception {
|
||||||
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectInSeparatedFile.kt");
|
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectInSeparatedFile.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("findAssociatedObjectLazyness.kt")
|
||||||
|
public void testFindAssociatedObjectLazyness() throws Exception {
|
||||||
|
runTest("js/js.translator/testData/box/reflection/findAssociatedObjectLazyness.kt");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
|
|||||||
Reference in New Issue
Block a user