Change ir const checker to match new rules of IntrinsicConstEvaluation

This commit is contained in:
Ivan Kylchik
2021-11-23 19:05:56 +03:00
parent 1431d4b356
commit 592baf134e
7 changed files with 100 additions and 57 deletions
@@ -358,13 +358,7 @@ private fun unfoldStringConcatenation(expression: IrStringConcatenation, environ
// TODO this check can be dropped after serialization introduction
// for now declarations in unsigned class don't have bodies and must be treated separately
if (state.irClass.defaultType.isUnsigned()) {
val result = when (val value = (state.fields.values.single() as Primitive<*>).value) {
is Byte -> value.toUByte().toString()
is Short -> value.toUShort().toString()
is Int -> value.toUInt().toString()
else -> (value as Number).toLong().toULong().toString()
}
return callStack.pushState(environment.convertToState(result, environment.irBuiltIns.stringType))
return callStack.pushState(environment.convertToState(state.unsignedToString(), environment.irBuiltIns.stringType))
}
val toStringCall = state.createToStringIrCall()
callStack.pushSimpleInstruction(toStringCall)
@@ -398,14 +392,16 @@ private fun unfoldFunctionReference(reference: IrFunctionReference, callStack: C
}
private fun unfoldPropertyReference(propertyReference: IrPropertyReference, callStack: CallStack) {
val getter = propertyReference.getter!!.owner
callStack.pushSimpleInstruction(propertyReference)
propertyReference.dispatchReceiver?.let { callStack.pushSimpleInstruction(getter.dispatchReceiverParameter!!) }
propertyReference.extensionReceiver?.let { callStack.pushSimpleInstruction(getter.extensionReceiverParameter!!) }
val getter = propertyReference.getter?.owner
if (getter != null) {
propertyReference.dispatchReceiver?.let { callStack.pushSimpleInstruction(getter.dispatchReceiverParameter!!) }
propertyReference.extensionReceiver?.let { callStack.pushSimpleInstruction(getter.extensionReceiverParameter!!) }
propertyReference.extensionReceiver?.let { callStack.pushCompoundInstruction(it) }
propertyReference.dispatchReceiver?.let { callStack.pushCompoundInstruction(it) }
propertyReference.extensionReceiver?.let { callStack.pushCompoundInstruction(it) }
propertyReference.dispatchReceiver?.let { callStack.pushCompoundInstruction(it) }
}
}
private fun unfoldClassReference(classReference: IrClassReference, callStack: CallStack) {
@@ -38,7 +38,7 @@ internal val TEMP_FUNCTION_FOR_INTERPRETER = object : IrDeclarationOriginImpl("T
fun Any?.toIrConstOrNull(irType: IrType, startOffset: Int = SYNTHETIC_OFFSET, endOffset: Int = SYNTHETIC_OFFSET): IrConst<*>? {
if (this == null) return IrConstImpl.constNull(startOffset, endOffset, irType)
val constType = irType.makeNotNull()
val constType = irType.makeNotNull().removeAnnotations()
return when (irType.getPrimitiveType()) {
PrimitiveType.BOOLEAN -> IrConstImpl.boolean(startOffset, endOffset, constType, this as Boolean)
PrimitiveType.CHAR -> IrConstImpl.char(startOffset, endOffset, constType, this as Char)
@@ -305,3 +305,12 @@ internal fun IrGetValue.isAccessToObject(): Boolean {
internal fun IrFunction.isAccessorOfPropertyWithBackingField(): Boolean {
return this is IrSimpleFunction && this.correspondingPropertySymbol?.owner?.backingField?.initializer != null
}
internal fun State.unsignedToString(): String {
return when (val value = (this.fields.values.single() as Primitive<*>).value) {
is Byte -> value.toUByte().toString()
is Short -> value.toUShort().toString()
is Int -> value.toUInt().toString()
else -> (value as Number).toLong().toULong().toString()
}
}
@@ -7,10 +7,7 @@ package org.jetbrains.kotlin.ir.interpreter.checker
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrGetObjectValue
import org.jetbrains.kotlin.ir.expressions.IrGetValue
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.interpreter.*
import org.jetbrains.kotlin.ir.interpreter.hasAnnotation
import org.jetbrains.kotlin.ir.types.isAny
@@ -22,21 +19,21 @@ import org.jetbrains.kotlin.name.FqName
enum class EvaluationMode(protected val mustCheckBody: Boolean) {
FULL(mustCheckBody = true) {
override fun canEvaluateFunction(function: IrFunction, expression: IrCall?): Boolean {
return true
}
override fun canEvaluateFunction(function: IrFunction, context: IrCall?): Boolean = true
override fun canEvaluateEnumValue(enumEntry: IrGetEnumValue, context: IrCall?): Boolean = true
override fun canEvaluateReference(reference: IrCallableReference<*>, context: IrCall?): Boolean = true
},
WITH_ANNOTATIONS(mustCheckBody = false) {
override fun canEvaluateFunction(function: IrFunction, expression: IrCall?): Boolean {
if (function.isCompileTimeProperty()) return true
override fun canEvaluateFunction(function: IrFunction, context: IrCall?): Boolean {
if (function.isCompileTimePropertyAccessor()) return true
return function.isMarkedAsCompileTime() || function.origin == IrBuiltIns.BUILTIN_OPERATOR ||
(function is IrSimpleFunction && function.isOperator && function.name.asString() == "invoke") ||
(function is IrSimpleFunction && function.isFakeOverride && function.overriddenSymbols.any { canEvaluateFunction(it.owner) }) ||
function.isCompileTimeTypeAlias()
}
private fun IrDeclaration?.isCompileTimeProperty(): Boolean {
private fun IrFunction?.isCompileTimePropertyAccessor(): Boolean {
val property = (this as? IrSimpleFunction)?.correspondingPropertySymbol?.owner ?: return false
if (property.isConst) return true
if (property.isMarkedAsCompileTime() || property.isCompileTimeTypeAlias()) return true
@@ -45,6 +42,9 @@ enum class EvaluationMode(protected val mustCheckBody: Boolean) {
val backingFieldExpression = backingField?.initializer?.expression as? IrGetValue
return backingFieldExpression?.origin == IrStatementOrigin.INITIALIZE_PROPERTY_FROM_PARAMETER
}
override fun canEvaluateEnumValue(enumEntry: IrGetEnumValue, context: IrCall?): Boolean = true
override fun canEvaluateReference(reference: IrCallableReference<*>, context: IrCall?): Boolean = true
},
ONLY_BUILTINS(mustCheckBody = false) {
@@ -52,7 +52,7 @@ enum class EvaluationMode(protected val mustCheckBody: Boolean) {
private val forbiddenMethodsOnStrings = setOf("subSequence", "hashCode", "<init>")
private val allowedExtensionFunctions = setOf("kotlin.floorDiv", "kotlin.mod", "kotlin.NumbersKt.floorDiv", "kotlin.NumbersKt.mod")
override fun canEvaluateFunction(function: IrFunction, expression: IrCall?): Boolean {
override fun canEvaluateFunction(function: IrFunction, context: IrCall?): Boolean {
if ((function as? IrSimpleFunction)?.correspondingPropertySymbol?.owner?.isConst == true) return true
val fqName = function.fqNameWhenAvailable?.asString()
@@ -62,26 +62,46 @@ enum class EvaluationMode(protected val mustCheckBody: Boolean) {
parentType == null -> fqName in allowedExtensionFunctions
parentType.isPrimitiveType() -> function.name.asString() !in forbiddenMethodsOnPrimitives
parentType.isString() -> function.name.asString() !in forbiddenMethodsOnStrings
parentType.isAny() -> function.name.asString() == "toString" && expression?.dispatchReceiver !is IrGetObjectValue
parentType.isAny() -> function.name.asString() == "toString" && context?.dispatchReceiver !is IrGetObjectValue
parent.isObject -> parent.parentClassOrNull?.defaultType?.let { it.isPrimitiveType() || it.isUnsigned() } == true
parentType.isUnsignedType() && function is IrConstructor -> true
else -> fqName in allowedExtensionFunctions
}
}
override fun canEvaluateEnumValue(enumEntry: IrGetEnumValue, context: IrCall?): Boolean = false
override fun canEvaluateReference(reference: IrCallableReference<*>, context: IrCall?): Boolean = false
},
ONLY_INTRINSIC_CONST(mustCheckBody = false) {
override fun canEvaluateFunction(function: IrFunction, expression: IrCall?): Boolean {
return function.isCompileTimeProperty() || function.isMarkedAsIntrinsicConst()
override fun canEvaluateFunction(function: IrFunction, context: IrCall?): Boolean {
return function.isCompileTimePropertyAccessor() || function.isMarkedAsIntrinsicConstEvaluation() || context.isIntrinsicConstEvaluationNameProperty()
}
private fun IrDeclaration?.isCompileTimeProperty(): Boolean {
private fun IrFunction?.isCompileTimePropertyAccessor(): Boolean {
val property = (this as? IrSimpleFunction)?.correspondingPropertySymbol?.owner ?: return false
return property.isConst || property.isMarkedAsIntrinsicConst()
return property.isConst || (property.resolveFakeOverride() ?: property).isMarkedAsIntrinsicConstEvaluation()
}
override fun canEvaluateEnumValue(enumEntry: IrGetEnumValue, context: IrCall?): Boolean {
return context.isIntrinsicConstEvaluationNameProperty()
}
override fun canEvaluateReference(reference: IrCallableReference<*>, context: IrCall?): Boolean {
return context.isIntrinsicConstEvaluationNameProperty()
}
private fun IrCall?.isIntrinsicConstEvaluationNameProperty(): Boolean {
if (this == null) return false
val owner = this.symbol.owner
val property = (owner as? IrSimpleFunction)?.correspondingPropertySymbol?.owner ?: return false
return owner.isCompileTimePropertyAccessor() && property.name.asString() == "name"
}
};
abstract fun canEvaluateFunction(function: IrFunction, expression: IrCall? = null): Boolean
abstract fun canEvaluateFunction(function: IrFunction, context: IrCall? = null): Boolean
abstract fun canEvaluateEnumValue(enumEntry: IrGetEnumValue, context: IrCall? = null): Boolean
abstract fun canEvaluateReference(reference: IrCallableReference<*>, context: IrCall? = null): Boolean
fun canEvaluateBody(function: IrFunction): Boolean {
if (function is IrSimpleFunction && function.correspondingPropertySymbol != null) return true
@@ -93,7 +113,7 @@ enum class EvaluationMode(protected val mustCheckBody: Boolean) {
)
fun IrDeclaration.isMarkedAsCompileTime() = isMarkedWith(compileTimeAnnotation)
protected fun IrDeclaration.isMarkedAsIntrinsicConst() = isMarkedWith(intrinsicConstEvaluationAnnotation)
protected fun IrDeclaration.isMarkedAsIntrinsicConstEvaluation() = isMarkedWith(intrinsicConstEvaluationAnnotation)
private fun IrDeclaration.isContract() = isMarkedWith(contractsDslAnnotation)
private fun IrDeclaration.isMarkedAsEvaluateIntrinsic() = isMarkedWith(evaluateIntrinsicAnnotation)
protected fun IrDeclaration.isCompileTimeTypeAlias() = this.parentClassOrNull?.fqName in compileTimeTypeAliases
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
class IrCompileTimeChecker(
containingDeclaration: IrElement? = null, private val mode: EvaluationMode = EvaluationMode.WITH_ANNOTATIONS
) : IrElementVisitor<Boolean, Nothing?> {
private var contextExpression: IrCall? = null
private val visitedStack = mutableListOf<IrElement>().apply { if (containingDeclaration != null) add(containingDeclaration) }
private fun IrElement.asVisited(block: () -> Boolean): Boolean {
@@ -29,24 +30,32 @@ class IrCompileTimeChecker(
return result
}
private fun <R> IrCall.saveContext(block: () -> R): R {
contextExpression = this
return block().apply { contextExpression = null }
}
override fun visitElement(element: IrElement, data: Nothing?) = false
private fun IrDeclarationParent.getInnerDeclarations(): List<IrStatement> {
return (this as? IrDeclarationContainer)?.declarations ?: (this as? IrStatementContainer)?.statements ?: emptyList()
}
private fun visitStatements(statements: List<IrStatement>, data: Nothing?): Boolean {
if (mode == EvaluationMode.ONLY_BUILTINS || mode == EvaluationMode.ONLY_INTRINSIC_CONST) {
val statement = statements.singleOrNull() ?: return false
return statement.accept(this, data)
private fun visitStatements(statements: List<IrStatement>): Boolean {
when (mode) {
EvaluationMode.ONLY_BUILTINS, EvaluationMode.ONLY_INTRINSIC_CONST -> {
val statement = statements.singleOrNull() ?: return false
return statement.accept(this, null)
}
else -> return statements.all { it.accept(this, null) }
}
return statements.all { it.accept(this, data) }
}
private fun visitConstructor(expression: IrFunctionAccessExpression): Boolean {
return when {
!visitValueParameters(expression, null) || !mode.canEvaluateFunction(expression.symbol.owner) -> false
else -> if (mode.canEvaluateBody(expression.symbol.owner)) expression.symbol.owner.body?.accept(this, null) != false else true
!visitValueParameters(expression, null) || !mode.canEvaluateFunction(expression.symbol.owner, contextExpression) -> false
mode.canEvaluateBody(expression.symbol.owner) -> expression.symbol.owner.body?.accept(this, null) != false
else -> true
}
}
@@ -54,12 +63,13 @@ class IrCompileTimeChecker(
val owner = expression.symbol.owner
if (!mode.canEvaluateFunction(owner, expression)) return false
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true
if (!visitValueParameters(expression, null)) return false
val bodyComputable = owner.asVisited { if (mode.canEvaluateBody(owner)) owner.body?.accept(this, null) ?: true else true }
return dispatchReceiverComputable && extensionReceiverComputable && bodyComputable
return expression.saveContext {
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true
if (!visitValueParameters(expression, null)) return@saveContext false
val bodyComputable = owner.asVisited { if (mode.canEvaluateBody(owner)) owner.body?.accept(this, null) ?: true else true }
return@saveContext dispatchReceiverComputable && extensionReceiverComputable && bodyComputable
}
}
override fun visitVariable(declaration: IrVariable, data: Nothing?): Boolean {
@@ -73,7 +83,7 @@ class IrCompileTimeChecker(
}
override fun visitBody(body: IrBody, data: Nothing?): Boolean {
return visitStatements(body.statements, data)
return visitStatements(body.statements)
}
// We need this separate method to explicitly indicate that IrExpressionBody can be interpreted in any evaluation mode
@@ -82,7 +92,10 @@ class IrCompileTimeChecker(
}
override fun visitBlock(expression: IrBlock, data: Nothing?): Boolean {
return visitStatements(expression.statements, data)
if (mode == EvaluationMode.ONLY_INTRINSIC_CONST && expression.origin == IrStatementOrigin.WHEN) {
return expression.statements.all { it.accept(this, null) }
}
return visitStatements(expression.statements)
}
override fun visitSyntheticBody(body: IrSyntheticBody, data: Nothing?): Boolean {
@@ -107,7 +120,7 @@ class IrCompileTimeChecker(
override fun visitComposite(expression: IrComposite, data: Nothing?): Boolean {
if (expression.origin == IrStatementOrigin.DESTRUCTURING_DECLARATION || expression.origin == null) {
return visitStatements(expression.statements, data)
return visitStatements(expression.statements)
}
return false
}
@@ -122,6 +135,7 @@ class IrCompileTimeChecker(
}
override fun visitGetEnumValue(expression: IrGetEnumValue, data: Nothing?): Boolean {
if (!mode.canEvaluateEnumValue(expression, contextExpression)) return false
return expression.symbol.owner.initializerExpression?.accept(this, data) == true
}
@@ -196,15 +210,13 @@ class IrCompileTimeChecker(
}
override fun visitFunctionReference(expression: IrFunctionReference, data: Nothing?): Boolean {
if (!mode.canEvaluateReference(expression, contextExpression)) return false
val owner = expression.symbol.owner
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true
if (mode == EvaluationMode.ONLY_INTRINSIC_CONST) {
return dispatchReceiverComputable && extensionReceiverComputable
} else if (!mode.canEvaluateFunction(owner)) {
return false
}
if (!mode.canEvaluateFunction(owner, contextExpression)) return false
val bodyComputable = owner.asVisited { if (mode.canEvaluateBody(owner)) owner.body?.accept(this, null) ?: true else true }
return dispatchReceiverComputable && extensionReceiverComputable && bodyComputable
@@ -273,13 +285,12 @@ class IrCompileTimeChecker(
}
override fun visitPropertyReference(expression: IrPropertyReference, data: Nothing?): Boolean {
if (!mode.canEvaluateReference(expression, contextExpression)) return false
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true
if (mode == EvaluationMode.ONLY_INTRINSIC_CONST) {
return dispatchReceiverComputable && extensionReceiverComputable
}
val getterIsComputable = expression.getter?.let { mode.canEvaluateFunction(it.owner) } ?: false
val getterIsComputable = expression.getter?.let { mode.canEvaluateFunction(it.owner, contextExpression) } ?: true
return dispatchReceiverComputable && extensionReceiverComputable && getterIsComputable
}
@@ -53,7 +53,7 @@ class IrConstTransformer(
initializer.expression = if (isConst) result.reportIfError(expression) else result.replaceIfError(expression)
}
return declaration
return super.visitField(declaration)
}
override fun visitDeclaration(declaration: IrDeclarationBase): IrStatement {
@@ -10,7 +10,9 @@ import org.jetbrains.kotlin.ir.interpreter.*
import org.jetbrains.kotlin.ir.interpreter.CallInterceptor
import org.jetbrains.kotlin.ir.interpreter.getDispatchReceiver
import org.jetbrains.kotlin.ir.interpreter.state.Common
import org.jetbrains.kotlin.ir.interpreter.state.Primitive
import org.jetbrains.kotlin.ir.interpreter.state.State
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.isFakeOverriddenFromAny
internal class CommonProxy private constructor(override val state: Common, override val callInterceptor: CallInterceptor) : Proxy {
@@ -53,6 +55,11 @@ internal class CommonProxy private constructor(override val state: Common, overr
}
override fun toString(): String {
// TODO this check can be dropped after serialization introduction
// for now declarations in unsigned class don't have bodies and must be treated separately
if (state.irClass.defaultType.isUnsigned()) {
return state.unsignedToString()
}
val valueArguments = mutableListOf<State>()
val toStringFun = state.getToStringFunction()
if (toStringFun.isFakeOverriddenFromAny() || toStringFun.wasAlreadyCalled()) return defaultToString()