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:
vladislav.grechko
2023-06-17 16:31:06 +02:00
committed by Space Team
parent f4f9f1f8f8
commit 38e9dfc823
5 changed files with 118 additions and 12 deletions
@@ -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>(
{
ConstEvaluationLowering(
@@ -457,7 +467,7 @@ val jvmLoweringPhases = buildJvmLoweringPhases("IrLowering", listOf("PerformByIr
private fun buildJvmLoweringPhases(
name: String,
phases: List<Pair<String, List<SameTypeNamedCompilerPhase<JvmBackendContext, IrFile>>>>
phases: List<Pair<String, List<SameTypeNamedCompilerPhase<JvmBackendContext, IrFile>>>>,
): SameTypeNamedCompilerPhase<JvmBackendContext, IrModuleFragment> {
return SameTypeNamedCompilerPhase(
name = name,
@@ -477,6 +487,7 @@ private fun buildJvmLoweringPhases(
repeatedAnnotationPhase then
functionInliningPhase then
apiVersionIsAtLeastEvaluationPhase then
createSeparateCallForInlinedLambdas then
markNecessaryInlinedClassesAsRegenerated then
@@ -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
}
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 {
@@ -2,7 +2,6 @@
// Wrong function resolution after package renaming
// IGNORE_BACKEND: ANDROID
// IGNORE_INLINER: IR
// FILE: 1.kt
package kotlin.internal
@@ -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"
}