From 971b4e63e74c110fe81393731119ea178702ff25 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Thu, 20 Jul 2023 00:52:22 +0200 Subject: [PATCH] JVM: support enumEntries intrinsic for Java & old Kotlin enums #KT-59710 Fixed --- .../inline/PsiInlineIntrinsicsSupport.kt | 5 + .../codegen/inline/ReifiedTypeInliner.kt | 21 ++-- ...LightTreeBlackBoxCodegenTestGenerated.java | 12 +++ .../FirPsiBlackBoxCodegenTestGenerated.java | 12 +++ .../EnumEntriesIntrinsicMappingsCacheImpl.kt | 96 +++++++++++++++++++ .../jvm/codegen/IrInlineIntrinsicsSupport.kt | 4 + .../backend/jvm/codegen/irCodegenUtils.kt | 13 +++ .../backend/jvm/intrinsics/EnumIntrinsics.kt | 8 +- .../kotlin/backend/jvm/JvmIrCodegenFactory.kt | 4 + .../jetbrains/kotlin/backend/jvm/JvmPhases.kt | 24 ++++- .../jvm/lower/EnumExternalEntriesLowering.kt | 31 ++---- .../jvm/lower/MappedEnumWhenLowering.kt | 13 +-- .../jvm/EnumEntriesIntrinsicMappingsCache.kt | 15 +++ .../kotlin/backend/jvm/JvmBackendContext.kt | 2 + .../kotlin/backend/jvm/ir/JvmIrUtils.kt | 27 ++++++ .../kotlin/codegen/InlineTestUtil.kt | 2 +- .../box/enum/enumEntriesIntrinsicForJava.kt | 29 ++++++ .../enum/enumEntriesIntrinsicMultipleEnums.kt | 50 ++++++++++ .../enumEntriesIntrinsicMultipleEnums.txt | 32 +++++++ .../codegen/boxInline/enum/javaEnumEntries.kt | 3 +- .../enum/javaEnumEntriesChainCapturedType.kt | 2 - .../enum/enumEntriesIntrinsicForJava.kt | 4 +- .../IrBlackBoxCodegenTestGenerated.java | 12 +++ ...kBoxCodegenWithIrInlinerTestGenerated.java | 12 +++ .../LightAnalysisModeTestGenerated.java | 10 ++ 25 files changed, 389 insertions(+), 54 deletions(-) create mode 100644 compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/EnumEntriesIntrinsicMappingsCacheImpl.kt create mode 100644 compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/EnumEntriesIntrinsicMappingsCache.kt create mode 100644 compiler/testData/codegen/box/enum/enumEntriesIntrinsicForJava.kt create mode 100644 compiler/testData/codegen/box/enum/enumEntriesIntrinsicMultipleEnums.kt create mode 100644 compiler/testData/codegen/box/enum/enumEntriesIntrinsicMultipleEnums.txt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/PsiInlineIntrinsicsSupport.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/PsiInlineIntrinsicsSupport.kt index fa4622ebb5a..73a16974053 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/PsiInlineIntrinsicsSupport.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/PsiInlineIntrinsicsSupport.kt @@ -24,6 +24,7 @@ import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.Type.INT_TYPE import org.jetbrains.org.objectweb.asm.Type.VOID_TYPE import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter +import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode class PsiInlineIntrinsicsSupport( override val state: GenerationState, @@ -72,6 +73,10 @@ class PsiInlineIntrinsicsSupport( override fun toKotlinType(type: KotlinType): KotlinType = type + override fun generateExternalEntriesForEnumTypeIfNeeded(type: KotlinType): FieldInsnNode? { + error("Not supported in the old JVM backend") + } + override fun reportSuspendTypeUnsupported() { state.diagnostics.report(TYPEOF_SUSPEND_TYPE.on(reportErrorsOn)) } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/ReifiedTypeInliner.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/ReifiedTypeInliner.kt index 185884fd875..6e22643a335 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/ReifiedTypeInliner.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/ReifiedTypeInliner.kt @@ -66,6 +66,8 @@ class ReifiedTypeInliner( fun toKotlinType(type: KT): KotlinType + fun generateExternalEntriesForEnumTypeIfNeeded(type: KT): FieldInsnNode? + fun reportSuspendTypeUnsupported() fun reportNonReifiedTypeParameterWithRecursiveBoundUnsupported(typeParameterName: Name) @@ -187,7 +189,7 @@ class ReifiedTypeInliner( OperationKind.SAFE_AS -> processAs(insn, instructions, type, asmType, safe = true) OperationKind.IS -> processIs(insn, instructions, type, asmType) OperationKind.JAVA_CLASS -> processJavaClass(insn, asmType) - OperationKind.ENUM_REIFIED -> processSpecialEnumFunction(insn, instructions, asmType) + OperationKind.ENUM_REIFIED -> processSpecialEnumFunction(insn, instructions, type, asmType) OperationKind.TYPE_OF -> processTypeOf(insn, instructions, type) } @@ -342,7 +344,7 @@ class ReifiedTypeInliner( return true } - private fun processSpecialEnumFunction(insn: MethodInsnNode, instructions: InsnList, parameter: Type): Boolean { + private fun processSpecialEnumFunction(insn: MethodInsnNode, instructions: InsnList, type: KT, parameter: Type): Boolean { val next1 = insn.next ?: return false val next2 = next1.next ?: return false if (next1.opcode == Opcodes.ACONST_NULL && next2.opcode == Opcodes.ALOAD) { @@ -362,12 +364,17 @@ class ReifiedTypeInliner( } else if (next1.opcode == Opcodes.ACONST_NULL && next2.opcode == Opcodes.CHECKCAST) { instructions.remove(next1) instructions.remove(next2) - // TODO: support enumEntries for Java enums. - instructions.insert( - insn, MethodInsnNode( - Opcodes.INVOKESTATIC, parameter.internalName, "getEntries", Type.getMethodDescriptor(AsmTypes.ENUM_ENTRIES), false + + val getField = intrinsicsSupport.generateExternalEntriesForEnumTypeIfNeeded(type) + if (getField != null) { + instructions.insert(insn, getField) + } else { + instructions.insert( + insn, MethodInsnNode( + Opcodes.INVOKESTATIC, parameter.internalName, "getEntries", Type.getMethodDescriptor(AsmTypes.ENUM_ENTRIES), false + ) ) - ) + } return true } diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenTestGenerated.java index 793ffcfa6cb..78e017c7808 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenTestGenerated.java @@ -16693,6 +16693,18 @@ public class FirLightTreeBlackBoxCodegenTestGenerated extends AbstractFirLightTr runTest("compiler/testData/codegen/box/enum/enumEntriesInCompanion.kt"); } + @Test + @TestMetadata("enumEntriesIntrinsicForJava.kt") + public void testEnumEntriesIntrinsicForJava() throws Exception { + runTest("compiler/testData/codegen/box/enum/enumEntriesIntrinsicForJava.kt"); + } + + @Test + @TestMetadata("enumEntriesIntrinsicMultipleEnums.kt") + public void testEnumEntriesIntrinsicMultipleEnums() throws Exception { + runTest("compiler/testData/codegen/box/enum/enumEntriesIntrinsicMultipleEnums.kt"); + } + @Test @TestMetadata("enumEntriesMultimodule.kt") public void testEnumEntriesMultimodule() throws Exception { diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirPsiBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirPsiBlackBoxCodegenTestGenerated.java index e21c7f86c6d..6eaee6b9966 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirPsiBlackBoxCodegenTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirPsiBlackBoxCodegenTestGenerated.java @@ -16693,6 +16693,18 @@ public class FirPsiBlackBoxCodegenTestGenerated extends AbstractFirPsiBlackBoxCo runTest("compiler/testData/codegen/box/enum/enumEntriesInCompanion.kt"); } + @Test + @TestMetadata("enumEntriesIntrinsicForJava.kt") + public void testEnumEntriesIntrinsicForJava() throws Exception { + runTest("compiler/testData/codegen/box/enum/enumEntriesIntrinsicForJava.kt"); + } + + @Test + @TestMetadata("enumEntriesIntrinsicMultipleEnums.kt") + public void testEnumEntriesIntrinsicMultipleEnums() throws Exception { + runTest("compiler/testData/codegen/box/enum/enumEntriesIntrinsicMultipleEnums.kt"); + } + @Test @TestMetadata("enumEntriesMultimodule.kt") public void testEnumEntriesMultimodule() throws Exception { diff --git a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/EnumEntriesIntrinsicMappingsCacheImpl.kt b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/EnumEntriesIntrinsicMappingsCacheImpl.kt new file mode 100644 index 00000000000..c4e8210ace1 --- /dev/null +++ b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/EnumEntriesIntrinsicMappingsCacheImpl.kt @@ -0,0 +1,96 @@ +/* + * 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 org.jetbrains.kotlin.backend.jvm.codegen + +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.common.lower.irBlockBody +import org.jetbrains.kotlin.backend.jvm.EnumEntriesIntrinsicMappingsCache +import org.jetbrains.kotlin.backend.jvm.JvmBackendContext +import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin +import org.jetbrains.kotlin.backend.jvm.ir.findEnumValuesFunction +import org.jetbrains.kotlin.ir.builders.declarations.addField +import org.jetbrains.kotlin.ir.builders.declarations.addFunction +import org.jetbrains.kotlin.ir.builders.declarations.buildClass +import org.jetbrains.kotlin.ir.builders.irCall +import org.jetbrains.kotlin.ir.builders.irSetField +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrField +import org.jetbrains.kotlin.ir.types.typeWith +import org.jetbrains.kotlin.ir.util.createImplicitParameterDeclarationWithWrappedDescriptor +import org.jetbrains.kotlin.ir.util.defaultType +import org.jetbrains.kotlin.load.java.JavaDescriptorVisibilities +import org.jetbrains.kotlin.name.Name + +// This class generates synthetic `$EntriesIntrinsicMappings` classes which cache the result of calling the `enumEntries` intrinsic +// on Java enums and on old (pre-1.9) Kotlin enums. +// +// `$EntriesIntrinsicMappings` classes are exactly the same as `$EntriesMappings` classes generated in `EnumExternalEntriesLowering` +// Unfortunately, we cannot reuse the logic from that lowering as is because, as opposed to the normal function call `E.entries`, +// the call to the intrinsic `enumEntries()` is fully reified only during the codegen, where all IR of the module has already been +// lowered, notably: +// 1) All the `$EntriesMappings` have already been created for the `E.entries` calls, so we cannot easily reuse those classes, which is the +// the reason why we're generating new classes with a slightly different name, even though it might be suboptimal in cases when you have +// both `E.entries` and `enumEntries` calls to the same enum in the same container. +// 2) Static initializers have been lowered, so we're generating the `` method manually, as opposed to adding static init sections +// as `EnumExternalEntriesLowering` does. +class EnumEntriesIntrinsicMappingsCacheImpl(private val context: JvmBackendContext) : EnumEntriesIntrinsicMappingsCache() { + private val storage = mutableMapOf() + + private inner class MappingsClass(val containingClass: IrClass) { + val irClass = context.irFactory.buildClass { + name = Name.identifier("EntriesIntrinsicMappings") + origin = JvmLoweredDeclarationOrigin.ENUM_MAPPINGS_FOR_ENTRIES + }.apply { + createImplicitParameterDeclarationWithWrappedDescriptor() + parent = containingClass + } + + val enums = hashMapOf() + } + + @Synchronized + override fun getEnumEntriesIntrinsicMappings(containingClass: IrClass, enumClass: IrClass): IrField { + val mappingsClass = storage.getOrPut(containingClass) { MappingsClass(containingClass) } + val field = mappingsClass.enums.getOrPut(enumClass) { + mappingsClass.irClass.addField { + name = Name.identifier("entries\$${mappingsClass.enums.size}") + type = context.ir.symbols.enumEntries.typeWith(enumClass.defaultType) + origin = JvmLoweredDeclarationOrigin.ENUM_MAPPINGS_FOR_ENTRIES + isFinal = true + isStatic = true + } + } + return field + } + + override fun generateMappingsClasses() { + val backendContext = context + for (klass in storage.values) { + // Use the same origin and visibility for `` as in StaticInitializersLowering. + val clinit = klass.irClass.addFunction( + "", context.irBuiltIns.unitType, visibility = JavaDescriptorVisibilities.PACKAGE_VISIBILITY, + isStatic = true, origin = JvmLoweredDeclarationOrigin.CLASS_STATIC_INITIALIZER + ) + clinit.body = context.createIrBuilder(clinit.symbol).irBlockBody(clinit) { + // Sort fields by name. Note that if there are a lot of calls to entries of different enums in the same container, it would + // result in the ordering "entries$0, entries$1, entries$10, entries$11, entries$12, ...", but it's not a big deal. + for ((enum, field) in klass.enums.entries.sortedBy { it.value.name }) { + // For each field, we're generating: + // entries$N = kotlin.enums.EnumEntriesKt.enumEntries(E.values()) + val enumValues = enum.findEnumValuesFunction(backendContext) + +irSetField( + null, field, + irCall(backendContext.ir.symbols.createEnumEntries).apply { + putValueArgument(0, irCall(enumValues)) + } + ) + } + } + + ClassCodegen.getOrCreate(klass.irClass, backendContext).generate() + } + } +} diff --git a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineIntrinsicsSupport.kt b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineIntrinsicsSupport.kt index 08be5134890..0442bc6658d 100644 --- a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineIntrinsicsSupport.kt +++ b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineIntrinsicsSupport.kt @@ -33,6 +33,7 @@ import org.jetbrains.org.objectweb.asm.Type.INT_TYPE import org.jetbrains.org.objectweb.asm.Type.VOID_TYPE import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode +import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode import org.jetbrains.org.objectweb.asm.tree.InsnList class IrInlineIntrinsicsSupport( @@ -119,6 +120,9 @@ class IrInlineIntrinsicsSupport( override fun toKotlinType(type: IrType): KotlinType = type.toIrBasedKotlinType() + override fun generateExternalEntriesForEnumTypeIfNeeded(type: IrType): FieldInsnNode? = + generateExternalEntriesForEnumTypeIfNeeded(type, classCodegen) + override fun reportSuspendTypeUnsupported() { classCodegen.context.ktDiagnosticReporter.at(reportErrorsOn, containingFile).report(JvmBackendErrors.TYPEOF_SUSPEND_TYPE) } diff --git a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/irCodegenUtils.kt b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/irCodegenUtils.kt index 9b7ed45f9d7..e5abb592551 100644 --- a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/irCodegenUtils.kt +++ b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/irCodegenUtils.kt @@ -38,6 +38,7 @@ import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmClassSignature import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Type +import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode import kotlin.collections.set class IrFrameMap : FrameMapBase() { @@ -349,3 +350,15 @@ val IrClass.reifiedTypeParameters: ReifiedTypeParametersUsages return tempReifiedTypeParametersUsages } + +internal fun generateExternalEntriesForEnumTypeIfNeeded(type: IrType, containingCodegen: ClassCodegen): FieldInsnNode? { + val irClass = type.getClass() + if (irClass == null || !irClass.isEnumClassWhichRequiresExternalEntries()) return null + + val mappingsCache = containingCodegen.context.enumEntriesIntrinsicMappingsCache + val field = mappingsCache.getEnumEntriesIntrinsicMappings(containingCodegen.irClass, irClass) + return FieldInsnNode( + Opcodes.GETSTATIC, containingCodegen.typeMapper.mapClass(field.parentAsClass).internalName, + field.name.asString(), AsmTypes.ENUM_ENTRIES.descriptor + ) +} diff --git a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/intrinsics/EnumIntrinsics.kt b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/intrinsics/EnumIntrinsics.kt index 40355e87f93..89d99034430 100644 --- a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/intrinsics/EnumIntrinsics.kt +++ b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/intrinsics/EnumIntrinsics.kt @@ -80,8 +80,12 @@ object EnumEntries : IntrinsicMethod() { mv.checkcast(AsmTypes.ENUM_ENTRIES) MaterialValue(codegen, AsmTypes.ENUM_ENTRIES, expression.type) } else { - // TODO: support enumEntries for Java enums. - mv.invokestatic(typeMapper.mapType(type).internalName, "getEntries", Type.getMethodDescriptor(AsmTypes.ENUM_ENTRIES), false) + val getField = generateExternalEntriesForEnumTypeIfNeeded(type, codegen.classCodegen) + if (getField != null) { + mv.visitFieldInsn(getField.opcode, getField.owner, getField.name, getField.desc) + } else { + mv.invokestatic(typeMapper.mapType(type).internalName, "getEntries", Type.getMethodDescriptor(AsmTypes.ENUM_ENTRIES), false) + } MaterialValue(codegen, AsmTypes.ENUM_ENTRIES, expression.type) } } diff --git a/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt b/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt index d1a0dbdd8a9..a45ff42f50f 100644 --- a/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt +++ b/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.backend.common.phaser.CompilerPhase import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel import org.jetbrains.kotlin.backend.common.serialization.DescriptorByIdSignatureFinderImpl +import org.jetbrains.kotlin.backend.jvm.codegen.EnumEntriesIntrinsicMappingsCacheImpl import org.jetbrains.kotlin.backend.jvm.codegen.JvmIrIntrinsicExtension import org.jetbrains.kotlin.backend.jvm.intrinsics.IrIntrinsicMethods import org.jetbrains.kotlin.backend.jvm.ir.getIoFile @@ -343,6 +344,9 @@ open class JvmIrCodegenFactory( context.getIntrinsic = { symbol: IrFunctionSymbol -> intrinsics.getIntrinsic(symbol) ?: generationExtensions.firstNotNullOfOrNull { it.getIntrinsic(symbol) } } + + context.enumEntriesIntrinsicMappingsCache = EnumEntriesIntrinsicMappingsCacheImpl(context) + /* JvmBackendContext creates new unbound symbols, have to resolve them. */ ExternalDependenciesGenerator(symbolTable, irProviders).generateUnboundSymbolsAsDependencies() diff --git a/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmPhases.kt b/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmPhases.kt index 1e6dd8d6aec..e977ccc5b4e 100644 --- a/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmPhases.kt +++ b/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmPhases.kt @@ -6,10 +6,7 @@ package org.jetbrains.kotlin.backend.jvm import org.jetbrains.kotlin.backend.common.FileLoweringPass -import org.jetbrains.kotlin.backend.common.phaser.SameTypeNamedCompilerPhase -import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase -import org.jetbrains.kotlin.backend.common.phaser.performByIrFile -import org.jetbrains.kotlin.backend.common.phaser.then +import org.jetbrains.kotlin.backend.common.phaser.* import org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrFile @@ -47,6 +44,22 @@ private class FileCodegen(private val context: JvmBackendContext, private val ge } } +private val generateAdditionalClassesPhase = SameTypeNamedCompilerPhase( + "GenerateAdditionalClasses", + "Generate additional classes that were requested during codegen", + lower = object : SameTypeCompilerPhase { + override fun invoke( + phaseConfig: PhaseConfigurationService, + phaserState: PhaserState, + context: JvmBackendContext, + input: IrModuleFragment, + ): IrModuleFragment { + context.enumEntriesIntrinsicMappingsCache.generateMappingsClasses() + return input + } + } +) + // Generate multifile facades first, to compute and store JVM signatures of const properties which are later used // when serializing metadata in the multifile parts. // TODO: consider dividing codegen itself into separate phases (bytecode generation, metadata serialization) to avoid this @@ -55,7 +68,8 @@ internal val jvmCodegenPhases = SameTypeNamedCompilerPhase( description = "Code generation", nlevels = 1, lower = codegenPhase(generateMultifileFacade = true) then - codegenPhase(generateMultifileFacade = false) + codegenPhase(generateMultifileFacade = false) then + generateAdditionalClassesPhase ) // This property is needed to avoid dependencies from "leaf" modules (cli, tests-common-new) on backend.jvm:lower. diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/EnumExternalEntriesLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/EnumExternalEntriesLowering.kt index 5d587908b6f..6a842239cdf 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/EnumExternalEntriesLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/EnumExternalEntriesLowering.kt @@ -11,7 +11,8 @@ import org.jetbrains.kotlin.backend.common.lower.createIrBuilder import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin -import org.jetbrains.kotlin.backend.jvm.ir.isInCurrentModule +import org.jetbrains.kotlin.backend.jvm.ir.findEnumValuesFunction +import org.jetbrains.kotlin.backend.jvm.ir.isEnumClassWhichRequiresExternalEntries import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.builders.declarations.addField @@ -26,7 +27,8 @@ import org.jetbrains.kotlin.ir.expressions.IrCall import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.impl.IrGetFieldImpl import org.jetbrains.kotlin.ir.types.typeWith -import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.ir.util.createImplicitParameterDeclarationWithWrappedDescriptor +import org.jetbrains.kotlin.ir.util.defaultType import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.SpecialNames @@ -60,6 +62,8 @@ internal val enumExternalEntriesPhase = makeIrFilePhase( * // F.kt * FKt$EntriesMappings.entries$1 * ``` + * + * There's similar code which handles the `enumEntries()` intrinsic code generation in `EnumEntriesIntrinsicMappingsCacheImpl`. */ class EnumExternalEntriesLowering(private val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoidWithContext() { @@ -99,33 +103,12 @@ class EnumExternalEntriesLowering(private val context: JvmBackendContext) : File override fun visitCall(expression: IrCall): IrExpression { val owner = expression.symbol.owner as? IrSimpleFunction val parentClass = owner?.parent as? IrClass ?: return super.visitCall(expression) - /* - * Candidates for lowering: - * * Java enums - * * Kotlin enums that have no 'getEntries' function (thus compiled with pre-1.8 LV/AV) - */ - val shouldBeLowered = parentClass.isEnumClass && - owner.name == SpecialNames.ENUM_GET_ENTRIES && - (parentClass.isFromJava() || !parentClass.hasEnumEntriesFunction()) + val shouldBeLowered = owner.name == SpecialNames.ENUM_GET_ENTRIES && parentClass.isEnumClassWhichRequiresExternalEntries() if (!shouldBeLowered) return super.visitCall(expression) val field = state!!.getEntriesFieldForEnum(parentClass) return IrGetFieldImpl(expression.startOffset, expression.endOffset, field.symbol, field.type) } - private fun IrClass.hasEnumEntriesFunction(): Boolean { - // Enums from other modules are always loaded with a property `entries` which has a getter ``. - // Enums from the current module will have a property `entries` if they are unlowered yet (i.e. enum is declared in another file - // which will be lowered after the file with the call site), or a function `` if they are already lowered. - return functions.any { it.isGetEntries() } - || (properties.any { it.getter?.isGetEntries() == true } && isInCurrentModule()) - } - - private fun IrSimpleFunction.isGetEntries(): Boolean = - name.toString() == "" - && dispatchReceiverParameter == null - && extensionReceiverParameter == null - && valueParameters.isEmpty() - override fun visitClassNew(declaration: IrClass): IrStatement { val oldState = state val mappingState = EntriesMappingState() diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/MappedEnumWhenLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/MappedEnumWhenLowering.kt index d5ec72fc512..2e25e25948c 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/MappedEnumWhenLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/MappedEnumWhenLowering.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.backend.common.lower.irCatch import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin +import org.jetbrains.kotlin.backend.jvm.ir.findEnumValuesFunction import org.jetbrains.kotlin.backend.jvm.ir.isInPublicInlineScope import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET @@ -23,7 +24,8 @@ import org.jetbrains.kotlin.ir.declarations.IrEnumEntry import org.jetbrains.kotlin.ir.declarations.IrField import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.impl.IrGetEnumValueImpl -import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.types.defaultType +import org.jetbrains.kotlin.ir.types.getClass import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.util.OperatorNameConventions @@ -167,12 +169,3 @@ private class MappedEnumWhenLowering(override val context: JvmBackendContext) : return declaration } } - -internal fun IrClass.findEnumValuesFunction(context: JvmBackendContext) = functions.single { - it.name.toString() == "values" - && it.dispatchReceiverParameter == null - && it.extensionReceiverParameter == null - && it.valueParameters.isEmpty() - && it.returnType.isBoxedArray - && it.returnType.getArrayElementType(context.irBuiltIns).classOrNull == this.symbol -} diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/EnumEntriesIntrinsicMappingsCache.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/EnumEntriesIntrinsicMappingsCache.kt new file mode 100644 index 00000000000..11238f0d80a --- /dev/null +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/EnumEntriesIntrinsicMappingsCache.kt @@ -0,0 +1,15 @@ +/* + * 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 org.jetbrains.kotlin.backend.jvm + +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrField + +abstract class EnumEntriesIntrinsicMappingsCache { + abstract fun getEnumEntriesIntrinsicMappings(containingClass: IrClass, enumClass: IrClass): IrField + + abstract fun generateMappingsClasses() +} diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt index 40494d1e6bd..c564eed5822 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt @@ -114,6 +114,8 @@ class JvmBackendContext( lateinit var getIntrinsic: (IrFunctionSymbol) -> IntrinsicMarker? + lateinit var enumEntriesIntrinsicMappingsCache: EnumEntriesIntrinsicMappingsCache + // Store evaluated SMAP for anonymous classes. Used only with IR inliner. val typeToCachedSMAP = mutableMapOf() diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrUtils.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrUtils.kt index 9954cd70020..180afc474c7 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrUtils.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrUtils.kt @@ -518,3 +518,30 @@ fun IrFunction.extensionReceiverName(state: GenerationState): String { fun IrFunction.isBridge(): Boolean = origin == IrDeclarationOrigin.BRIDGE || origin == IrDeclarationOrigin.BRIDGE_SPECIAL + +// Enum requires external implementation of entries if it's either a Java enum, or a Kotlin enum compiled with pre-1.8 LV/AV. +fun IrClass.isEnumClassWhichRequiresExternalEntries(): Boolean = + isEnumClass && (isFromJava() || !hasEnumEntriesFunction()) + +private fun IrClass.hasEnumEntriesFunction(): Boolean { + // Enums from other modules are always loaded with a property `entries` which has a getter ``. + // Enums from the current module will have a property `entries` if they are unlowered yet (i.e. enum is declared in another file + // which will be lowered after the file with the call site), or a function `` if they are already lowered. + return functions.any { it.isGetEntries() } + || (properties.any { it.getter?.isGetEntries() == true } && isInCurrentModule()) +} + +private fun IrSimpleFunction.isGetEntries(): Boolean = + name.toString() == "" + && dispatchReceiverParameter == null + && extensionReceiverParameter == null + && valueParameters.isEmpty() + +fun IrClass.findEnumValuesFunction(context: JvmBackendContext): IrSimpleFunction = functions.single { + it.name.toString() == "values" + && it.dispatchReceiverParameter == null + && it.extensionReceiverParameter == null + && it.valueParameters.isEmpty() + && it.returnType.isBoxedArray + && it.returnType.getArrayElementType(context.irBuiltIns).classOrNull == this.symbol +} diff --git a/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/codegen/InlineTestUtil.kt b/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/codegen/InlineTestUtil.kt index 3282e80af21..607c6affdf9 100644 --- a/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/codegen/InlineTestUtil.kt +++ b/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/codegen/InlineTestUtil.kt @@ -226,7 +226,7 @@ object InlineTestUtil { override fun equals(other: Any?): Boolean = throw UnsupportedOperationException() override fun toString(): String = throw UnsupportedOperationException() } - }!! + } ?: error("Generated class file has no @Metadata annotation: $file") private class InlineInfo(val inlineMethods: Set, val binaryClasses: Map) diff --git a/compiler/testData/codegen/box/enum/enumEntriesIntrinsicForJava.kt b/compiler/testData/codegen/box/enum/enumEntriesIntrinsicForJava.kt new file mode 100644 index 00000000000..48b2df1fd05 --- /dev/null +++ b/compiler/testData/codegen/box/enum/enumEntriesIntrinsicForJava.kt @@ -0,0 +1,29 @@ +// TARGET_BACKEND: JVM_IR +// !OPT_IN: kotlin.ExperimentalStdlibApi +// WITH_STDLIB +// FILE: Z.java + +public enum Z { + O, K +} + +// FILE: box.kt + +@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // TODO: remove once KT-53154 is fixed. + +import kotlin.enums.* + +fun callFromOtherFunctionInTheSameFile(): EnumEntries = enumEntries() + +fun box(): String { + val z = enumEntries() + if (z.toString() != "[O, K]") return "Fail 1: $z" + + val z2 = enumEntries() + if (z2 !== z) return "Fail 2: another instance of EnumEntries is created" + + val z3 = callFromOtherFunctionInTheSameFile() + if (z3 !== z) return "Fail 3: another instance of EnumEntries is created" + + return "OK" +} diff --git a/compiler/testData/codegen/box/enum/enumEntriesIntrinsicMultipleEnums.kt b/compiler/testData/codegen/box/enum/enumEntriesIntrinsicMultipleEnums.kt new file mode 100644 index 00000000000..0ccf3c5ec7a --- /dev/null +++ b/compiler/testData/codegen/box/enum/enumEntriesIntrinsicMultipleEnums.kt @@ -0,0 +1,50 @@ +// TARGET_BACKEND: JVM_IR +// WITH_STDLIB + +// CHECK_BYTECODE_LISTING +// ^ Check that there's only one $EntriesIntrinsicMappings class, with three fields (entries$0, entries$1, entries$2). + +// MODULE: lib +// !LANGUAGE: +EnumEntries +// FILE: X.kt + +enum class X { + X1, X2 +} + +// FILE: Y.java + +public enum Y { + Y1, Y2 +} + +// FILE: Z.java + +public enum Z { + Z1, Z2 +} + +// MODULE: box(lib) +// !LANGUAGE: +EnumEntries +// !OPT_IN: kotlin.ExperimentalStdlibApi +// FILE: box.kt + +@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // TODO: remove once KT-53154 is fixed. + +import kotlin.enums.enumEntries + +fun box(): String { + val x = enumEntries() + if (x.toString() != "[X1, X2]") return "Fail X: $x" + + val y = enumEntries() + if (y.toString() != "[Y1, Y2]") return "Fail Y: $y" + + val z = enumEntries() + if (z.toString() != "[Z1, Z2]") return "Fail Z: $z" + + val xx = enumEntries() + if (xx.toString() != "[X1, X2]") return "Fail X #2: $xx" + + return "OK" +} diff --git a/compiler/testData/codegen/box/enum/enumEntriesIntrinsicMultipleEnums.txt b/compiler/testData/codegen/box/enum/enumEntriesIntrinsicMultipleEnums.txt new file mode 100644 index 00000000000..f4a0878c6eb --- /dev/null +++ b/compiler/testData/codegen/box/enum/enumEntriesIntrinsicMultipleEnums.txt @@ -0,0 +1,32 @@ +Module: lib +@kotlin.Metadata +public final enum class X { + // source: 'X.kt' + private synthetic final static field $ENTRIES: kotlin.enums.EnumEntries + private synthetic final static field $VALUES: X[] + public final enum static field X1: X + public final enum static field X2: X + private synthetic final static method $values(): X[] + static method (): void + private method (p0: java.lang.String, p1: int): void + public static @org.jetbrains.annotations.NotNull method getEntries(): kotlin.enums.EnumEntries + public static method valueOf(p0: java.lang.String): X + public static method values(): X[] +} +Module: box +@kotlin.Metadata +public synthetic final class BoxKt$EntriesIntrinsicMappings { + // source: 'box.kt' + public synthetic final static field entries$0: kotlin.enums.EnumEntries + public synthetic final static field entries$1: kotlin.enums.EnumEntries + public synthetic final static field entries$2: kotlin.enums.EnumEntries + static method (): void + public synthetic inner class BoxKt$EntriesIntrinsicMappings +} + +@kotlin.Metadata +public final class BoxKt { + // source: 'box.kt' + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String + public synthetic inner class BoxKt$EntriesIntrinsicMappings +} diff --git a/compiler/testData/codegen/boxInline/enum/javaEnumEntries.kt b/compiler/testData/codegen/boxInline/enum/javaEnumEntries.kt index 40d89c58180..a464e1a179d 100644 --- a/compiler/testData/codegen/boxInline/enum/javaEnumEntries.kt +++ b/compiler/testData/codegen/boxInline/enum/javaEnumEntries.kt @@ -1,6 +1,5 @@ -// IGNORE_BACKEND: JVM_IR - // TARGET_BACKEND: JVM_IR +// NO_CHECK_LAMBDA_INLINING // !OPT_IN: kotlin.ExperimentalStdlibApi // WITH_STDLIB // FILE: test/Z.java diff --git a/compiler/testData/codegen/boxInline/enum/javaEnumEntriesChainCapturedType.kt b/compiler/testData/codegen/boxInline/enum/javaEnumEntriesChainCapturedType.kt index f49f89f2a72..1b447dff3fb 100644 --- a/compiler/testData/codegen/boxInline/enum/javaEnumEntriesChainCapturedType.kt +++ b/compiler/testData/codegen/boxInline/enum/javaEnumEntriesChainCapturedType.kt @@ -1,5 +1,3 @@ -// IGNORE_BACKEND: JVM_IR - // TARGET_BACKEND: JVM_IR // WITH_STDLIB // !OPT_IN: kotlin.ExperimentalStdlibApi diff --git a/compiler/testData/codegen/bytecodeText/enum/enumEntriesIntrinsicForJava.kt b/compiler/testData/codegen/bytecodeText/enum/enumEntriesIntrinsicForJava.kt index 68be2497e8e..9073c9ae18f 100644 --- a/compiler/testData/codegen/bytecodeText/enum/enumEntriesIntrinsicForJava.kt +++ b/compiler/testData/codegen/bytecodeText/enum/enumEntriesIntrinsicForJava.kt @@ -19,4 +19,6 @@ fun foo() { enumEntries2() } -// 0 INVOKESTATIC kotlin/enums/EnumEntriesKt.enumEntries +// There should be one call to enumEntries in _1Kt$EntriesIntrinsicMappings.. +// 2 GETSTATIC _1Kt\$EntriesIntrinsicMappings.entries\$0 +// 1 INVOKESTATIC kotlin/enums/EnumEntriesKt.enumEntries diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java index 5e8e52ce040..9160293b348 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java @@ -16693,6 +16693,18 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/enum/enumEntriesInCompanion.kt"); } + @Test + @TestMetadata("enumEntriesIntrinsicForJava.kt") + public void testEnumEntriesIntrinsicForJava() throws Exception { + runTest("compiler/testData/codegen/box/enum/enumEntriesIntrinsicForJava.kt"); + } + + @Test + @TestMetadata("enumEntriesIntrinsicMultipleEnums.kt") + public void testEnumEntriesIntrinsicMultipleEnums() throws Exception { + runTest("compiler/testData/codegen/box/enum/enumEntriesIntrinsicMultipleEnums.kt"); + } + @Test @TestMetadata("enumEntriesMultimodule.kt") public void testEnumEntriesMultimodule() throws Exception { diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenWithIrInlinerTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenWithIrInlinerTestGenerated.java index 9b824abea33..f46453a5c1f 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenWithIrInlinerTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenWithIrInlinerTestGenerated.java @@ -16693,6 +16693,18 @@ public class IrBlackBoxCodegenWithIrInlinerTestGenerated extends AbstractIrBlack runTest("compiler/testData/codegen/box/enum/enumEntriesInCompanion.kt"); } + @Test + @TestMetadata("enumEntriesIntrinsicForJava.kt") + public void testEnumEntriesIntrinsicForJava() throws Exception { + runTest("compiler/testData/codegen/box/enum/enumEntriesIntrinsicForJava.kt"); + } + + @Test + @TestMetadata("enumEntriesIntrinsicMultipleEnums.kt") + public void testEnumEntriesIntrinsicMultipleEnums() throws Exception { + runTest("compiler/testData/codegen/box/enum/enumEntriesIntrinsicMultipleEnums.kt"); + } + @Test @TestMetadata("enumEntriesMultimodule.kt") public void testEnumEntriesMultimodule() throws Exception { diff --git a/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index c4fe74a9289..6d2e23124da 100644 --- a/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -13786,6 +13786,16 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes runTest("compiler/testData/codegen/box/enum/enumEntriesInCompanion.kt"); } + @TestMetadata("enumEntriesIntrinsicForJava.kt") + public void testEnumEntriesIntrinsicForJava() throws Exception { + runTest("compiler/testData/codegen/box/enum/enumEntriesIntrinsicForJava.kt"); + } + + @TestMetadata("enumEntriesIntrinsicMultipleEnums.kt") + public void testEnumEntriesIntrinsicMultipleEnums() throws Exception { + runTest("compiler/testData/codegen/box/enum/enumEntriesIntrinsicMultipleEnums.kt"); + } + @TestMetadata("enumEntriesMultimodule.kt") public void testEnumEntriesMultimodule() throws Exception { runTest("compiler/testData/codegen/box/enum/enumEntriesMultimodule.kt");