[K/N] Basic support of Enum.entries for Native
No forward compatibility yet, with old klib IR linker will fail. ^KT-53324
This commit is contained in:
+12
@@ -16323,6 +16323,18 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
|
||||
runTest("compiler/testData/codegen/box/enum/enumEntriesMultimoduleMultipleMappingsForMultipleClassfiles.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntriesMultimoduleNoMappings.kt")
|
||||
public void testEnumEntriesMultimoduleNoMappings() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/enum/enumEntriesMultimoduleNoMappings.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntriesNameClashes.kt")
|
||||
public void testEnumEntriesNameClashes() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/enum/enumEntriesNameClashes.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntryMembers.kt")
|
||||
public void testEnumEntryMembers() throws Exception {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// !LANGUAGE: +EnumEntries
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
// FULL_JDK
|
||||
// TARGET_BACKEND: NATIVE
|
||||
// WITH_STDLIB
|
||||
|
||||
enum class MyEnum {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
// FULL_JDK
|
||||
// AFTER KT-53649 - TARGET_BACKEND: NATIVE
|
||||
// WITH_STDLIB
|
||||
|
||||
// MODULE: lib
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
// FULL_JDK
|
||||
// AFTER KT-53649 - TARGET_BACKEND: NATIVE
|
||||
// WITH_STDLIB
|
||||
|
||||
// MODULE: lib
|
||||
|
||||
Vendored
+1
-1
@@ -1,5 +1,5 @@
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
// FULL_JDK
|
||||
// AFTER KT-53649 - TARGET_BACKEND: NATIVE
|
||||
// WITH_STDLIB
|
||||
|
||||
// MODULE: lib
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: NATIVE
|
||||
// WITH_STDLIB
|
||||
|
||||
// MODULE: lib
|
||||
// !LANGUAGE: +EnumEntries
|
||||
// FILE: MyEnum.kt
|
||||
enum class MyEnum {
|
||||
Nope, OK
|
||||
}
|
||||
|
||||
// MODULE: caller(lib)
|
||||
// !LANGUAGE: +EnumEntries
|
||||
// FILE: Box.kt
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun box(): String {
|
||||
return MyEnum.entries[1].toString()
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// !LANGUAGE: +EnumEntries
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// TARGET_BACKEND: NATIVE
|
||||
// WITH_STDLIB
|
||||
|
||||
enum class EnumWithClash {
|
||||
values,
|
||||
entries,
|
||||
valueOf;
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun box(): String {
|
||||
val ref = EnumWithClash::entries
|
||||
if (ref().toString() != "[values, entries, valueOf]") return "FAIL 1"
|
||||
if (EnumWithClash.entries.toString() != "entries") return "FAIL 2"
|
||||
return "OK"
|
||||
}
|
||||
+12
@@ -16323,6 +16323,18 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
runTest("compiler/testData/codegen/box/enum/enumEntriesMultimoduleMultipleMappingsForMultipleClassfiles.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntriesMultimoduleNoMappings.kt")
|
||||
public void testEnumEntriesMultimoduleNoMappings() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/enum/enumEntriesMultimoduleNoMappings.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntriesNameClashes.kt")
|
||||
public void testEnumEntriesNameClashes() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/enum/enumEntriesNameClashes.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntryMembers.kt")
|
||||
public void testEnumEntryMembers() throws Exception {
|
||||
|
||||
+7
-2
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
* Copyright 2010-2022 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 org.jetbrains.kotlin.backend.konan.ir
|
||||
@@ -303,6 +303,11 @@ internal class KonanSymbols(
|
||||
|
||||
val valueOfForEnum = internalFunction("valueOfForEnum")
|
||||
|
||||
val createEnumEntries = irBuiltIns.findFunctions(Name.identifier("enumEntries"), "kotlin", "enums")
|
||||
.single { it.descriptor.valueParameters.singleOrNull()?.type?.constructor?.declarationDescriptor == array.descriptor }
|
||||
|
||||
val enumEntriesInterface = irBuiltIns.findClass(Name.identifier("EnumEntries"), "kotlin", "enums")!!
|
||||
|
||||
val createUninitializedInstance = internalFunction("createUninitializedInstance")
|
||||
|
||||
val initInstance = internalFunction("initInstance")
|
||||
|
||||
+82
-33
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
* Copyright 2010-2022 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 org.jetbrains.kotlin.backend.konan.lower
|
||||
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.backend.konan.Context
|
||||
import org.jetbrains.kotlin.backend.konan.MemoryModel
|
||||
import org.jetbrains.kotlin.backend.konan.NativeMapping
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName
|
||||
import org.jetbrains.kotlin.backend.konan.ir.KonanNameConventions
|
||||
import org.jetbrains.kotlin.backend.konan.ir.KonanSymbols
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.IntrinsicType
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.tryGetIntrinsicType
|
||||
@@ -51,6 +52,7 @@ internal class EnumsSupport(
|
||||
private val enumValueGetters = mapping.enumValueGetters
|
||||
private val enumEntriesMaps = mapping.enumEntriesMaps
|
||||
private val array = symbols.array
|
||||
private val enumEntries = symbols.enumEntriesInterface
|
||||
private val genericValueOfSymbol = symbols.valueOfForEnum
|
||||
private val genericValuesSymbol = symbols.valuesForEnum
|
||||
|
||||
@@ -75,7 +77,7 @@ internal class EnumsSupport(
|
||||
startOffset = enumClass.startOffset
|
||||
endOffset = enumClass.endOffset
|
||||
origin = DECLARATION_ORIGIN_ENUM
|
||||
name = "OBJECT".synthesizedName
|
||||
name = implObjectName
|
||||
kind = ClassKind.OBJECT
|
||||
}.apply {
|
||||
superTypes = listOf(irBuiltIns.anyType)
|
||||
@@ -85,11 +87,20 @@ internal class EnumsSupport(
|
||||
startOffset = enumClass.startOffset
|
||||
endOffset = enumClass.endOffset
|
||||
origin = DECLARATION_ORIGIN_ENUM
|
||||
name = "VALUES".synthesizedName
|
||||
name = valuesFieldName
|
||||
type = array.typeWith(enumClass.defaultType)
|
||||
visibility = DescriptorVisibilities.PRIVATE
|
||||
isFinal = true
|
||||
})
|
||||
addChild(irFactory.buildField {
|
||||
startOffset = enumClass.startOffset
|
||||
endOffset = enumClass.endOffset
|
||||
origin = DECLARATION_ORIGIN_ENUM
|
||||
name = entriesFieldName
|
||||
type = enumEntries.typeWith(enumClass.defaultType)
|
||||
visibility = DescriptorVisibilities.PRIVATE
|
||||
isFinal = true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,7 +126,8 @@ internal class EnumsSupport(
|
||||
}
|
||||
}
|
||||
|
||||
fun getValuesField(implObject: IrClass) = implObject.fields.single()
|
||||
fun getValuesField(implObject: IrClass) = implObject.fields.single { it.name == valuesFieldName }
|
||||
fun getEntriesField(implObject: IrClass) = implObject.fields.single { it.name == entriesFieldName }
|
||||
|
||||
fun IrBuilderWithScope.irGetValuesField(enumClass: IrClass): IrExpression {
|
||||
val implObject = getImplObject(enumClass)
|
||||
@@ -123,16 +135,28 @@ internal class EnumsSupport(
|
||||
return irGetField(irGetObject(implObject.symbol), valuesField)
|
||||
}
|
||||
|
||||
fun IrBuilderWithScope.irEnumValues(enumClass: IrClass) =
|
||||
fun IrBuilderWithScope.irEnumValues(enumClass: IrClass) : IrExpression =
|
||||
irCall(genericValuesSymbol, listOf(enumClass.defaultType)).apply {
|
||||
putValueArgument(0, irGetValuesField(enumClass))
|
||||
}
|
||||
|
||||
fun IrBuilderWithScope.irEnumValueOf(enumClass: IrClass, value: IrExpression) =
|
||||
fun IrBuilderWithScope.irEnumValueOf(enumClass: IrClass, value: IrExpression) : IrExpression =
|
||||
irCall(genericValueOfSymbol, listOf(enumClass.defaultType)).apply {
|
||||
putValueArgument(0, value)
|
||||
putValueArgument(1, irGetValuesField(enumClass))
|
||||
}
|
||||
|
||||
fun IrBuilderWithScope.irEnumEntries(enumClass: IrClass) : IrExpression {
|
||||
val implObject = getImplObject(enumClass)
|
||||
val entriesField = getEntriesField(implObject)
|
||||
return irGetField(irGetObject(implObject.symbol), entriesField)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val valuesFieldName = "VALUES".synthesizedName
|
||||
val entriesFieldName = "ENTRIES".synthesizedName
|
||||
val implObjectName = "OBJECT".synthesizedName
|
||||
}
|
||||
}
|
||||
|
||||
internal object DECLARATION_ORIGIN_ENUM : IrDeclarationOriginImpl("ENUM")
|
||||
@@ -201,8 +225,9 @@ internal class EnumClassLowering(val context: Context) : FileLoweringPass {
|
||||
private val enumsSupport = context.enumsSupport
|
||||
private val symbols = context.ir.symbols
|
||||
private val createUninitializedInstance = symbols.createUninitializedInstance
|
||||
private val createEnumEntries = symbols.createEnumEntries
|
||||
private val initInstance = symbols.initInstance
|
||||
private val arrayGet = symbols.arrayGet[symbols.array]!!
|
||||
private val arrayGet = symbols.array.owner.functions.single { it.name == KonanNameConventions.getWithoutBoundCheck }.symbol
|
||||
private val constructorOfAny = context.irBuiltIns.anyClass.owner.constructors.first()
|
||||
|
||||
override fun lower(irFile: IrFile) {
|
||||
@@ -219,6 +244,7 @@ internal class EnumClassLowering(val context: Context) : FileLoweringPass {
|
||||
private inner class EnumClassTransformer(val irClass: IrClass) {
|
||||
private val implObject = enumsSupport.getImplObject(irClass)
|
||||
private val valuesField = enumsSupport.getValuesField(implObject)
|
||||
private val entriesField = enumsSupport.getEntriesField(implObject)
|
||||
private val enumEntriesMap = enumsSupport.enumEntriesMap(irClass)
|
||||
|
||||
fun run() {
|
||||
@@ -237,28 +263,27 @@ internal class EnumClassLowering(val context: Context) : FileLoweringPass {
|
||||
declaration.correspondingClass = null
|
||||
listOfNotNull(correspondingClass)
|
||||
}
|
||||
|
||||
is IrFunction -> {
|
||||
val body = declaration.body
|
||||
if (body is IrSyntheticBody) {
|
||||
declaration.body = when (body.kind) {
|
||||
IrSyntheticBodyKind.ENUM_VALUEOF -> context.createIrBuilder(declaration.symbol).irBlockBody(declaration) {
|
||||
+irReturn(with(enumsSupport) { irEnumValueOf(irClass, irGet(declaration.valueParameters[0])) })
|
||||
}
|
||||
|
||||
IrSyntheticBodyKind.ENUM_VALUES -> context.createIrBuilder(declaration.symbol).irBlockBody(declaration) {
|
||||
+irReturn(with(enumsSupport) { irEnumValues(irClass) })
|
||||
}
|
||||
|
||||
IrSyntheticBodyKind.ENUM_ENTRIES -> TODO("KT-48872 is not yet supported")
|
||||
}
|
||||
}
|
||||
null
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
irClass.simpleFunctions().forEach { declaration ->
|
||||
val body = declaration.body
|
||||
if (body is IrSyntheticBody) {
|
||||
declaration.body = when (body.kind) {
|
||||
IrSyntheticBodyKind.ENUM_VALUEOF -> context.createIrBuilder(declaration.symbol).irBlockBody(declaration) {
|
||||
+irReturn(with(enumsSupport) { irEnumValueOf(irClass, irGet(declaration.valueParameters[0])) })
|
||||
}
|
||||
|
||||
IrSyntheticBodyKind.ENUM_VALUES -> context.createIrBuilder(declaration.symbol).irBlockBody(declaration) {
|
||||
+irReturn(with(enumsSupport) { irEnumValues(irClass) })
|
||||
}
|
||||
|
||||
IrSyntheticBodyKind.ENUM_ENTRIES -> context.createIrBuilder(declaration.symbol).irBlockBody(declaration) {
|
||||
+irReturn(with(enumsSupport) { irEnumEntries(irClass) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return enumEntries
|
||||
}
|
||||
|
||||
@@ -297,6 +322,16 @@ internal class EnumClassLowering(val context: Context) : FileLoweringPass {
|
||||
statements += buildValuesFieldInitializer(enumEntries)
|
||||
// Split allocation and constructors calling because enum entries can reference one another.
|
||||
statements += callEnumEntriesConstructors(enumEntries)
|
||||
statements += buildEntriesFieldInitializer()
|
||||
|
||||
// Needed for legacy MM targets that do not support threads.
|
||||
if (this@EnumClassLowering.context.memoryModel != MemoryModel.EXPERIMENTAL) {
|
||||
statements += irBuilder.irBlock {
|
||||
irCall(this@EnumClassLowering.context.ir.symbols.freeze, listOf(valuesField.type)).apply {
|
||||
extensionReceiver = irGet(implObject.thisReceiver!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
irClass.companionObject()?.let { statements += irBuilder.irGetObject(it.symbol) }
|
||||
}
|
||||
@@ -331,6 +366,26 @@ internal class EnumClassLowering(val context: Context) : FileLoweringPass {
|
||||
irSetField(irGet(implObject.thisReceiver!!), valuesField, irValuesInitializer)
|
||||
}
|
||||
|
||||
private fun buildEntriesFieldInitializer() = irBuilder.irBlock {
|
||||
val irValuesArray = irTemporary(irGetField(irGet(implObject.thisReceiver!!), valuesField))
|
||||
val irEntriesArray = this@EnumClassLowering.context.createArrayOfExpression(
|
||||
startOffset, endOffset,
|
||||
irClass.defaultType,
|
||||
enumEntriesMap.values
|
||||
.sortedBy { it.ordinal }
|
||||
.map {
|
||||
irCall(arrayGet, irClass.defaultType).apply {
|
||||
dispatchReceiver = irGet(irValuesArray)
|
||||
putValueArgument(0, irInt(it.getterId))
|
||||
}
|
||||
}
|
||||
)
|
||||
val irEntriesInitializer = irCall(createEnumEntries, listOf(irClass.defaultType)).apply {
|
||||
putValueArgument(0, irEntriesArray)
|
||||
}
|
||||
+irSetField(irGet(implObject.thisReceiver!!), entriesField, irEntriesInitializer)
|
||||
}
|
||||
|
||||
private fun callEnumEntriesConstructors(enumEntries: List<IrEnumEntry>) = irBuilder.irBlock {
|
||||
val receiver = implObject.thisReceiver!!
|
||||
val instances = irTemporary(irGetField(irGet(receiver), valuesField))
|
||||
@@ -354,12 +409,6 @@ internal class EnumClassLowering(val context: Context) : FileLoweringPass {
|
||||
else -> error("Unexpected initializer: $initializer")
|
||||
}
|
||||
}
|
||||
// Needed for legacy MM targets that do not support threads.
|
||||
if (this@EnumClassLowering.context.memoryModel != MemoryModel.EXPERIMENTAL) {
|
||||
+irCall(this@EnumClassLowering.context.ir.symbols.freeze, listOf(valuesField.type)).apply {
|
||||
extensionReceiver = irGet(receiver)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4737,6 +4737,7 @@ interopTest("interop_union") {
|
||||
interopTest("interop_enums") {
|
||||
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
|
||||
interop = 'cenums'
|
||||
flags = ['-XXLanguage:+EnumEntries']
|
||||
source = "interop/basics/enums.kt"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
/*
|
||||
* Copyright 2010-2022 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.
|
||||
*/
|
||||
|
||||
import cenums.*
|
||||
import kotlinx.cinterop.*
|
||||
import kotlin.test.*
|
||||
|
||||
@OptIn(kotlin.ExperimentalStdlibApi::class)
|
||||
fun main() {
|
||||
memScoped {
|
||||
val e = alloc<E.Var>()
|
||||
@@ -12,4 +18,12 @@ fun main() {
|
||||
e.value = TODO()
|
||||
}
|
||||
}
|
||||
val values = E.values()
|
||||
assertEquals(values[0], E.A)
|
||||
assertEquals(values[1], E.B)
|
||||
assertEquals(values[2], E.C)
|
||||
val entries = E.entries
|
||||
assertEquals(entries[0], E.A)
|
||||
assertEquals(entries[1], E.B)
|
||||
assertEquals(entries[2], E.C)
|
||||
}
|
||||
@@ -27,7 +27,18 @@ internal fun <E : Enum<E>> enumEntries(entriesProvider: () -> Array<E>): EnumEnt
|
||||
@PublishedApi
|
||||
@ExperimentalStdlibApi
|
||||
@SinceKotlin("1.8") // Used by Native/JS compilers and Java serialization
|
||||
internal fun <E : Enum<E>> enumEntries(entries: Array<E>): EnumEntries<E> = EnumEntriesList { entries }
|
||||
internal fun <E : Enum<E>> enumEntries(entries: Array<E>): EnumEntries<E> = EnumEntriesList { entries }.also {
|
||||
/*
|
||||
* Here we are enforcing initialization of _entries property.
|
||||
* It is required because of two reasons.
|
||||
* 1. In old Native mm the object will be frozen after creation, so it must be immutable
|
||||
* 2. Native doesn't support @Volatile for now, so this initialization is not generally safe, if
|
||||
* done after object is published.
|
||||
*
|
||||
* This is very implementation-dependent hack, and it should be removed when/if both reasons above are gone.
|
||||
*/
|
||||
it.size
|
||||
}
|
||||
|
||||
/*
|
||||
* For enum class E, this class is instantiated in the following manner (NB it's pseudocode that does not
|
||||
|
||||
+18
@@ -13513,6 +13513,24 @@ public class NativeCodegenBoxTestGenerated extends AbstractNativeCodegenBoxTest
|
||||
runTest("compiler/testData/codegen/box/enum/enumConstructorParameterClashWithDefaults.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntries.kt")
|
||||
public void testEnumEntries() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/enum/enumEntries.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntriesMultimoduleNoMappings.kt")
|
||||
public void testEnumEntriesMultimoduleNoMappings() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/enum/enumEntriesMultimoduleNoMappings.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntriesNameClashes.kt")
|
||||
public void testEnumEntriesNameClashes() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/enum/enumEntriesNameClashes.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntryMembers.kt")
|
||||
public void testEnumEntryMembers() throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user