[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:
Pavel Kunyavskiy
2022-08-18 17:23:55 +02:00
committed by Space
parent 8cde6fe3c5
commit 895a8ff149
14 changed files with 199 additions and 40 deletions
@@ -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
View File
@@ -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,5 +1,5 @@
// TARGET_BACKEND: JVM_IR
// FULL_JDK
// AFTER KT-53649 - TARGET_BACKEND: NATIVE
// WITH_STDLIB
// MODULE: lib
@@ -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"
}
@@ -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 {
@@ -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")
@@ -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
@@ -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 {