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>(
|
||||
{
|
||||
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
|
||||
|
||||
|
||||
+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
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
-1
@@ -2,7 +2,6 @@
|
||||
|
||||
// Wrong function resolution after package renaming
|
||||
// IGNORE_BACKEND: ANDROID
|
||||
// IGNORE_INLINER: IR
|
||||
|
||||
// FILE: 1.kt
|
||||
package kotlin.internal
|
||||
|
||||
+12
-4
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user