From 592baf134e8630374856f52ba93ccd2f31f63577 Mon Sep 17 00:00:00 2001 From: Ivan Kylchik Date: Tue, 23 Nov 2021 19:05:56 +0300 Subject: [PATCH] Change ir const checker to match new rules of `IntrinsicConstEvaluation` --- .../ir/interpreter/InstructionsUnfolder.kt | 20 +++--- .../kotlin/ir/interpreter/IrTreeBuildUtils.kt | 2 +- .../jetbrains/kotlin/ir/interpreter/Utils.kt | 9 +++ .../ir/interpreter/checker/EvaluationMode.kt | 56 +++++++++++------ .../checker/IrCompileTimeChecker.kt | 61 +++++++++++-------- .../interpreter/checker/IrConstTransformer.kt | 2 +- .../ir/interpreter/proxy/CommonProxy.kt | 7 +++ 7 files changed, 100 insertions(+), 57 deletions(-) diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/InstructionsUnfolder.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/InstructionsUnfolder.kt index 8d0f4bb8dfb..0cc3c682784 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/InstructionsUnfolder.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/InstructionsUnfolder.kt @@ -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) { diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrTreeBuildUtils.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrTreeBuildUtils.kt index 03747a00804..1e1b4d9fa4e 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrTreeBuildUtils.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrTreeBuildUtils.kt @@ -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) diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/Utils.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/Utils.kt index 6a3292e8f79..e8b664b536b 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/Utils.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/Utils.kt @@ -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() + } +} diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/EvaluationMode.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/EvaluationMode.kt index a9a98d5f485..1e6a0ef6af6 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/EvaluationMode.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/EvaluationMode.kt @@ -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", "") 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 diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrCompileTimeChecker.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrCompileTimeChecker.kt index dc9e02adf91..b04e989ba52 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrCompileTimeChecker.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrCompileTimeChecker.kt @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.ir.visitors.IrElementVisitor class IrCompileTimeChecker( containingDeclaration: IrElement? = null, private val mode: EvaluationMode = EvaluationMode.WITH_ANNOTATIONS ) : IrElementVisitor { + private var contextExpression: IrCall? = null private val visitedStack = mutableListOf().apply { if (containingDeclaration != null) add(containingDeclaration) } private fun IrElement.asVisited(block: () -> Boolean): Boolean { @@ -29,24 +30,32 @@ class IrCompileTimeChecker( return result } + private fun 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 { return (this as? IrDeclarationContainer)?.declarations ?: (this as? IrStatementContainer)?.statements ?: emptyList() } - private fun visitStatements(statements: List, 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): 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 } diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrConstTransformer.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrConstTransformer.kt index 1ccfeca7533..a5f32338395 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrConstTransformer.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/checker/IrConstTransformer.kt @@ -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 { diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/CommonProxy.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/CommonProxy.kt index 21513008ff0..2f133216338 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/CommonProxy.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/CommonProxy.kt @@ -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() val toStringFun = state.getToStringFunction() if (toStringFun.isFakeOverriddenFromAny() || toStringFun.wasAlreadyCalled()) return defaultToString()