diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt index 852c8351078..1adbb8888f2 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt @@ -317,6 +317,16 @@ internal val functionInliningPhase = makeIrModulePhase( ) ) +private val apiVersionIsAtLeastEvaluationPhase = makeIrModulePhase( + { context -> + if (!context.irInlinerIsEnabled()) return@makeIrModulePhase FileLoweringPass.Empty + ApiVersionIsAtLeastEvaluationLowering(context) + }, + name = "ApiVersionIsAtLeastEvaluationLowering", + description = "Evaluate inlined invocations of `apiVersionIsAtLeast`", + prerequisite = setOf(functionInliningPhase) +) + private val constEvaluationPhase = makeIrModulePhase( { ConstEvaluationLowering( @@ -457,7 +467,7 @@ val jvmLoweringPhases = buildJvmLoweringPhases("IrLowering", listOf("PerformByIr private fun buildJvmLoweringPhases( name: String, - phases: List>>> + phases: List>>>, ): SameTypeNamedCompilerPhase { return SameTypeNamedCompilerPhase( name = name, @@ -477,6 +487,7 @@ private fun buildJvmLoweringPhases( repeatedAnnotationPhase then functionInliningPhase then + apiVersionIsAtLeastEvaluationPhase then createSeparateCallForInlinedLambdas then markNecessaryInlinedClassesAsRegenerated then diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/ApiVersionIsAtLeastEvaluationLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/ApiVersionIsAtLeastEvaluationLowering.kt new file mode 100644 index 00000000000..da416334fb8 --- /dev/null +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/ApiVersionIsAtLeastEvaluationLowering.kt @@ -0,0 +1,86 @@ +/* + * 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.lower + +import org.jetbrains.kotlin.backend.common.FileLoweringPass +import org.jetbrains.kotlin.backend.jvm.JvmBackendContext +import org.jetbrains.kotlin.backend.jvm.ir.createJvmIrBuilder +import org.jetbrains.kotlin.backend.jvm.ir.getIntConstArgumentOrNull +import org.jetbrains.kotlin.builtins.StandardNames +import org.jetbrains.kotlin.config.MavenComparableVersion +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.builders.irFalse +import org.jetbrains.kotlin.ir.builders.irTrue +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.expressions.IrBlock +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrInlinedFunctionBlock +import org.jetbrains.kotlin.ir.types.isInt +import org.jetbrains.kotlin.ir.util.getPackageFragment +import org.jetbrains.kotlin.ir.util.isFacadeClass +import org.jetbrains.kotlin.ir.visitors.IrElementTransformer + +internal class ApiVersionIsAtLeastEvaluationLowering(val context: JvmBackendContext) : FileLoweringPass, + IrElementTransformer { + + private val apiVersion = context.state.languageVersionSettings.apiVersion.version + + data class Data(val currentFunction: IrFunction?, val isInsideInlinedBlock: Boolean) + + override fun lower(irFile: IrFile) { + irFile.accept(this, Data(currentFunction = null, isInsideInlinedBlock = false)) + } + + override fun visitBlock(expression: IrBlock, data: Data): IrExpression { + return super.visitBlock( + expression, + data.copy(isInsideInlinedBlock = data.isInsideInlinedBlock || expression is IrInlinedFunctionBlock) + ) + } + + override fun visitFunction(declaration: IrFunction, data: Data): IrStatement { + return super.visitFunction(declaration, data.copy(currentFunction = declaration)) + } + + override fun visitCall(expression: IrCall, data: Data): IrExpression { + if (!data.isInsideInlinedBlock + || !expression.symbol.owner.isApiVersionIsAtLeast + || isInInlineFunInKotlinRuntime(data.currentFunction) + ) { + return super.visitCall(expression, data) as IrExpression + } + + val epic = expression.getIntConstArgumentOrNull(0) ?: return super.visitCall(expression, data) as IrExpression + val major = expression.getIntConstArgumentOrNull(1) ?: return super.visitCall(expression, data) as IrExpression + val minor = expression.getIntConstArgumentOrNull(2) ?: return super.visitCall(expression, data) as IrExpression + + val currentFunction = data.currentFunction + require(currentFunction != null) + val builder = context.createJvmIrBuilder(currentFunction.symbol) + val versionArgument = MavenComparableVersion("$epic.$major.$minor") + return if (apiVersion >= versionArgument) builder.irTrue() else builder.irFalse() + } + + private fun isInInlineFunInKotlinRuntime(currentFunction: IrFunction?): Boolean { + return currentFunction != null && currentFunction.isInline + && currentFunction.getPackageFragment().packageFqName.startsWith(StandardNames.BUILT_INS_PACKAGE_NAME) + } + + private val IrFunction.isApiVersionIsAtLeast: Boolean + get() { + return name.asString() == "apiVersionIsAtLeast" + && getPackageFragment().packageFqName == StandardNames.KOTLIN_INTERNAL_FQ_NAME + && parent.isFacadeClass + && valueParameters.size == 3 + && valueParameters[0].type.isInt() + && valueParameters[1].type.isInt() + && valueParameters[2].type.isInt() + && dispatchReceiverParameter == null + && extensionReceiverParameter == null + } +} \ No newline at end of file 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 dd8595e6915..34eb0bf27e1 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 @@ -416,13 +416,15 @@ fun findSuperDeclaration(function: IrSimpleFunction, isSuperCall: Boolean, jvmDe return current } +fun IrMemberAccessExpression<*>.getIntConstArgumentOrNull(i: Int) = getValueArgument(i)?.let { + if (it is IrConst<*> && it.kind == IrConstKind.Int) + it.value as Int + else + null +} + fun IrMemberAccessExpression<*>.getIntConstArgument(i: Int): Int = - getValueArgument(i)?.let { - if (it is IrConst<*> && it.kind == IrConstKind.Int) - it.value as Int - else - null - } ?: throw AssertionError("Value argument #$i should be an Int const: ${dump()}") + getIntConstArgumentOrNull(i) ?: throw AssertionError("Value argument #$i should be an Int const: ${dump()}") fun IrMemberAccessExpression<*>.getStringConstArgument(i: Int): String = getValueArgument(i)?.let { diff --git a/compiler/testData/codegen/boxInline/bytecodePreprocessing/apiVersionAtLeast1.kt b/compiler/testData/codegen/boxInline/bytecodePreprocessing/apiVersionAtLeast1.kt index 7f40caa9c2a..0f90a108ae2 100644 --- a/compiler/testData/codegen/boxInline/bytecodePreprocessing/apiVersionAtLeast1.kt +++ b/compiler/testData/codegen/boxInline/bytecodePreprocessing/apiVersionAtLeast1.kt @@ -2,7 +2,6 @@ // Wrong function resolution after package renaming // IGNORE_BACKEND: ANDROID -// IGNORE_INLINER: IR // FILE: 1.kt package kotlin.internal diff --git a/compiler/testData/codegen/boxInline/bytecodePreprocessing/inlineApiVersionAtLeastInStdlibInlineFunction.kt b/compiler/testData/codegen/boxInline/bytecodePreprocessing/inlineApiVersionAtLeastInStdlibInlineFunction.kt index d986f0f1bea..beecb0d9bd3 100644 --- a/compiler/testData/codegen/boxInline/bytecodePreprocessing/inlineApiVersionAtLeastInStdlibInlineFunction.kt +++ b/compiler/testData/codegen/boxInline/bytecodePreprocessing/inlineApiVersionAtLeastInStdlibInlineFunction.kt @@ -8,17 +8,25 @@ // FILE: A.kt package kotlin.internal + fun apiVersionIsAtLeast(epic: Int, major: Int, minor: Int): Boolean { return false } -inline fun versionDependentInlineFun() = if (apiVersionIsAtLeast(1, 1, 0)) "Fail" else "OK" -inline fun test() = versionDependentInlineFun() + +inline fun versionDependentInlineFun() = if (apiVersionIsAtLeast(1, 1, 0)) true else false +inline fun testInline() = versionDependentInlineFun() + +fun testNonInline() = versionDependentInlineFun() // FILE: B.kt import kotlin.internal.* fun box(): String { val clazz = Class.forName("kotlin.internal.AKt") - val func = clazz.methods.single { it.name == "test" } - return func.invoke(null) as String + val func = clazz.methods.single { it.name == "testInline" } + if (func.invoke(null) as Boolean == true) return "Fail 1" + + if (!testNonInline()) return "Fail 2" + + return "OK" } \ No newline at end of file