Evaluate apiVersionIsAtLeast on compile time while IR-inlining
IR inliner should evaluate `apiVersionIsAtLeast` on compile-time (except cases of inlining to inline functions from kotlin runtime) just as bytecode inliner does. ^KT-59291: Fixed
This commit is contained in:
committed by
Space Team
parent
f4f9f1f8f8
commit
38e9dfc823
@@ -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<JvmBackendContext>(
|
private val constEvaluationPhase = makeIrModulePhase<JvmBackendContext>(
|
||||||
{
|
{
|
||||||
ConstEvaluationLowering(
|
ConstEvaluationLowering(
|
||||||
@@ -457,7 +467,7 @@ val jvmLoweringPhases = buildJvmLoweringPhases("IrLowering", listOf("PerformByIr
|
|||||||
|
|
||||||
private fun buildJvmLoweringPhases(
|
private fun buildJvmLoweringPhases(
|
||||||
name: String,
|
name: String,
|
||||||
phases: List<Pair<String, List<SameTypeNamedCompilerPhase<JvmBackendContext, IrFile>>>>
|
phases: List<Pair<String, List<SameTypeNamedCompilerPhase<JvmBackendContext, IrFile>>>>,
|
||||||
): SameTypeNamedCompilerPhase<JvmBackendContext, IrModuleFragment> {
|
): SameTypeNamedCompilerPhase<JvmBackendContext, IrModuleFragment> {
|
||||||
return SameTypeNamedCompilerPhase(
|
return SameTypeNamedCompilerPhase(
|
||||||
name = name,
|
name = name,
|
||||||
@@ -477,6 +487,7 @@ private fun buildJvmLoweringPhases(
|
|||||||
repeatedAnnotationPhase then
|
repeatedAnnotationPhase then
|
||||||
|
|
||||||
functionInliningPhase then
|
functionInliningPhase then
|
||||||
|
apiVersionIsAtLeastEvaluationPhase then
|
||||||
createSeparateCallForInlinedLambdas then
|
createSeparateCallForInlinedLambdas then
|
||||||
markNecessaryInlinedClassesAsRegenerated then
|
markNecessaryInlinedClassesAsRegenerated then
|
||||||
|
|
||||||
|
|||||||
+86
@@ -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<ApiVersionIsAtLeastEvaluationLowering.Data> {
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -416,13 +416,15 @@ fun findSuperDeclaration(function: IrSimpleFunction, isSuperCall: Boolean, jvmDe
|
|||||||
return current
|
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 =
|
fun IrMemberAccessExpression<*>.getIntConstArgument(i: Int): Int =
|
||||||
getValueArgument(i)?.let {
|
getIntConstArgumentOrNull(i) ?: throw AssertionError("Value argument #$i should be an Int const: ${dump()}")
|
||||||
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()}")
|
|
||||||
|
|
||||||
fun IrMemberAccessExpression<*>.getStringConstArgument(i: Int): String =
|
fun IrMemberAccessExpression<*>.getStringConstArgument(i: Int): String =
|
||||||
getValueArgument(i)?.let {
|
getValueArgument(i)?.let {
|
||||||
|
|||||||
-1
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
// Wrong function resolution after package renaming
|
// Wrong function resolution after package renaming
|
||||||
// IGNORE_BACKEND: ANDROID
|
// IGNORE_BACKEND: ANDROID
|
||||||
// IGNORE_INLINER: IR
|
|
||||||
|
|
||||||
// FILE: 1.kt
|
// FILE: 1.kt
|
||||||
package kotlin.internal
|
package kotlin.internal
|
||||||
|
|||||||
+12
-4
@@ -8,17 +8,25 @@
|
|||||||
// FILE: A.kt
|
// FILE: A.kt
|
||||||
|
|
||||||
package kotlin.internal
|
package kotlin.internal
|
||||||
|
|
||||||
fun apiVersionIsAtLeast(epic: Int, major: Int, minor: Int): Boolean {
|
fun apiVersionIsAtLeast(epic: Int, major: Int, minor: Int): Boolean {
|
||||||
return false
|
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
|
// FILE: B.kt
|
||||||
import kotlin.internal.*
|
import kotlin.internal.*
|
||||||
|
|
||||||
fun box(): String {
|
fun box(): String {
|
||||||
val clazz = Class.forName("kotlin.internal.AKt")
|
val clazz = Class.forName("kotlin.internal.AKt")
|
||||||
val func = clazz.methods.single { it.name == "test" }
|
val func = clazz.methods.single { it.name == "testInline" }
|
||||||
return func.invoke(null) as String
|
if (func.invoke(null) as Boolean == true) return "Fail 1"
|
||||||
|
|
||||||
|
if (!testNonInline()) return "Fail 2"
|
||||||
|
|
||||||
|
return "OK"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user