[IR] Handle each interpreter checker one by one

This wy we can interpret all expressions like `A::a.name` at first and
after that evaluate all complex expressions like
`A::a.name + A::b.name`.
This commit is contained in:
Ivan Kylchik
2023-04-28 14:28:50 +02:00
committed by Space Team
parent e58e20fc7b
commit 70560fc3eb
10 changed files with 80 additions and 66 deletions
@@ -9,11 +9,10 @@ import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.interpreter.IrInterpreterConfiguration
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
interface IrInterpreterChecker : IrElementVisitor<Boolean, IrInterpreterCheckerData> {
val mode: EvaluationMode
}
interface IrInterpreterChecker : IrElementVisitor<Boolean, IrInterpreterCheckerData>
class IrInterpreterCheckerData(
val mode: EvaluationMode,
val irBuiltIns: IrBuiltIns,
val interpreterConfiguration: IrInterpreterConfiguration
val interpreterConfiguration: IrInterpreterConfiguration,
)
@@ -17,9 +17,7 @@ import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.statements
class IrInterpreterCommonChecker(
override val mode: EvaluationMode
) : IrInterpreterChecker {
class IrInterpreterCommonChecker : IrInterpreterChecker {
private val visitedStack = mutableListOf<IrElement>()
private inline fun IrElement.asVisited(crossinline block: () -> Boolean): Boolean {
@@ -42,14 +40,14 @@ class IrInterpreterCommonChecker(
private fun visitConstructor(expression: IrFunctionAccessExpression, data: IrInterpreterCheckerData): Boolean {
val constructor = expression.symbol.owner
if (!mode.canEvaluateFunction(constructor)) return false
if (!data.mode.canEvaluateFunction(constructor)) return false
if (!visitValueArguments(expression, data)) return false
return visitBodyIfNeeded(constructor, data) &&
constructor.parentAsClass.declarations.filterIsInstance<IrAnonymousInitializer>().all { it.accept(this, data) }
}
private fun visitBodyIfNeeded(irFunction: IrFunction, data: IrInterpreterCheckerData): Boolean {
if (!mode.mustCheckBodyOf(irFunction)) return true
if (!data.mode.mustCheckBodyOf(irFunction)) return true
return irFunction.asVisited { irFunction.body?.accept(this@IrInterpreterCommonChecker, data) ?: true }
}
@@ -58,10 +56,10 @@ class IrInterpreterCommonChecker(
}
override fun visitCall(expression: IrCall, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateExpression(expression)) return false
if (!data.mode.canEvaluateExpression(expression)) return false
val owner = expression.symbol.owner
if (!mode.canEvaluateFunction(owner)) return false
if (!data.mode.canEvaluateFunction(owner)) return false
// We disable `toFloat` folding on K/JS till `toFloat` is fixed (KT-35422)
// This check must be placed here instead of CallInterceptor because we still
@@ -101,7 +99,7 @@ class IrInterpreterCommonChecker(
}
override fun visitBlock(expression: IrBlock, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateBlock(expression)) return false
if (!data.mode.canEvaluateBlock(expression)) return false
// `IrReturnableBlock` will be created from IrCall after inline. We should do basically the same check as for IrCall.
if (expression is IrReturnableBlock) {
@@ -113,7 +111,7 @@ class IrInterpreterCommonChecker(
}
override fun visitComposite(expression: IrComposite, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateComposite(expression)) return false
if (!data.mode.canEvaluateComposite(expression)) return false
return visitStatements(expression.statements, data)
}
@@ -125,7 +123,7 @@ class IrInterpreterCommonChecker(
override fun visitConst(expression: IrConst<*>, data: IrInterpreterCheckerData): Boolean {
if (expression.type.getUnsignedType() != null) {
val constructor = expression.type.classOrNull?.owner?.constructors?.singleOrNull() ?: return false
return mode.canEvaluateFunction(constructor)
return data.mode.canEvaluateFunction(constructor)
}
return true
}
@@ -146,7 +144,7 @@ class IrInterpreterCommonChecker(
.filterIsInstance<IrSimpleFunction>()
.single { it.name.asString() == "toString" && it.valueParameters.isEmpty() && it.extensionReceiverParameter == null }
mode.canEvaluateFunction(toString) && visitBodyIfNeeded(toString, data)
data.mode.canEvaluateFunction(toString) && visitBodyIfNeeded(toString, data)
}
else -> arg.accept(this, data)
@@ -160,7 +158,7 @@ class IrInterpreterCommonChecker(
}
override fun visitGetEnumValue(expression: IrGetEnumValue, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateEnumValue(expression)) return false
if (!data.mode.canEvaluateEnumValue(expression)) return false
// we want to avoid recursion in cases like "enum class E(val srt: String) { OK(OK.name) }"
if (visitedStack.contains(expression)) return true
@@ -242,20 +240,20 @@ class IrInterpreterCommonChecker(
}
override fun visitFunctionReference(expression: IrFunctionReference, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateCallableReference(expression)) return false
if (!data.mode.canEvaluateCallableReference(expression)) return false
val owner = expression.symbol.owner
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, data) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, data) ?: true
if (!mode.canEvaluateFunction(owner)) return false
if (!data.mode.canEvaluateFunction(owner)) return false
val bodyComputable = visitBodyIfNeeded(owner, data)
return dispatchReceiverComputable && extensionReceiverComputable && bodyComputable
}
override fun visitFunctionExpression(expression: IrFunctionExpression, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateFunctionExpression(expression)) return false
if (!data.mode.canEvaluateFunctionExpression(expression)) return false
val body = expression.function.body ?: return false
return expression.function.asVisited { body.accept(this, data) }
@@ -275,7 +273,7 @@ class IrInterpreterCommonChecker(
}
override fun visitWhen(expression: IrWhen, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateExpression(expression)) return false
if (!data.mode.canEvaluateExpression(expression)) return false
return expression.branches.all { it.accept(this, data) }
}
@@ -297,7 +295,7 @@ class IrInterpreterCommonChecker(
}
override fun visitTry(aTry: IrTry, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateExpression(aTry)) return false
if (!data.mode.canEvaluateExpression(aTry)) return false
if (!aTry.tryResult.accept(this, data)) return false
if (aTry.finallyExpression != null && aTry.finallyExpression?.accept(this, data) == false) return false
@@ -314,22 +312,22 @@ class IrInterpreterCommonChecker(
}
override fun visitThrow(expression: IrThrow, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateExpression(expression)) return false
if (!data.mode.canEvaluateExpression(expression)) return false
return expression.value.accept(this, data)
}
override fun visitPropertyReference(expression: IrPropertyReference, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateCallableReference(expression)) return false
if (!data.mode.canEvaluateCallableReference(expression)) return false
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, data) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, data) ?: true
val getterIsComputable = expression.getter?.let { mode.canEvaluateFunction(it.owner) } ?: true
val getterIsComputable = expression.getter?.let { data.mode.canEvaluateFunction(it.owner) } ?: true
return dispatchReceiverComputable && extensionReceiverComputable && getterIsComputable
}
override fun visitClassReference(expression: IrClassReference, data: IrInterpreterCheckerData): Boolean {
return mode.canEvaluateClassReference(expression)
return data.mode.canEvaluateClassReference(expression)
}
}
@@ -16,14 +16,12 @@ import org.jetbrains.kotlin.ir.expressions.IrGetEnumValue
import org.jetbrains.kotlin.ir.expressions.IrStringConcatenation
import org.jetbrains.kotlin.ir.util.isSubclassOf
class IrInterpreterNameChecker(
override val mode: EvaluationMode,
) : IrInterpreterChecker {
class IrInterpreterNameChecker : IrInterpreterChecker {
override fun visitElement(element: IrElement, data: IrInterpreterCheckerData) = false
override fun visitCall(expression: IrCall, data: IrInterpreterCheckerData): Boolean {
val owner = expression.symbol.owner
if (!mode.canEvaluateFunction(owner)) return false
if (!data.mode.canEvaluateFunction(owner)) return false
return expression.isKCallableNameCall(data.irBuiltIns) || expression.isEnumName()
}
@@ -8,8 +8,10 @@ package org.jetbrains.kotlin.ir.interpreter.preprocessor
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrCallableReference
import org.jetbrains.kotlin.ir.expressions.IrGetValue
import org.jetbrains.kotlin.ir.expressions.impl.IrCompositeImpl
import org.jetbrains.kotlin.ir.interpreter.checker.IrInterpreterNameChecker.Companion.isKCallableNameCall
import org.jetbrains.kotlin.name.SpecialNames
// Note: this class still will not allow us to evaluate things like `A()::a.name + `A()::b.name`.
// This code will be optimized but not completely turned into "ab" result.
@@ -22,8 +24,10 @@ class IrInterpreterKCallableNamePreprocessor : IrInterpreterPreprocessor {
// receiver is needed for bound callable reference
val receiver = callableReference.dispatchReceiver ?: callableReference.extensionReceiver ?: return expression
callableReference.dispatchReceiver = null
callableReference.extensionReceiver = null
if (receiver is IrGetValue && receiver.symbol.owner.name == SpecialNames.THIS) return expression
return IrCompositeImpl(
expression.startOffset, expression.endOffset, expression.type, origin = null, statements = listOf(receiver, expression)
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrVarargImpl
import org.jetbrains.kotlin.ir.interpreter.IrInterpreter
import org.jetbrains.kotlin.ir.interpreter.checker.EvaluationMode
import org.jetbrains.kotlin.ir.interpreter.checker.IrInterpreterChecker
import org.jetbrains.kotlin.ir.interpreter.isPrimitiveArray
import org.jetbrains.kotlin.ir.interpreter.toIrConst
import org.jetbrains.kotlin.ir.types.*
@@ -22,11 +23,12 @@ internal abstract class IrConstAnnotationTransformer(
interpreter: IrInterpreter,
irFile: IrFile,
mode: EvaluationMode,
checker: IrInterpreterChecker,
evaluatedConstTracker: EvaluatedConstTracker?,
onWarning: (IrFile, IrElement, IrErrorExpression) -> Unit,
onError: (IrFile, IrElement, IrErrorExpression) -> Unit,
suppressExceptions: Boolean,
) : IrConstTransformer(interpreter, irFile, mode, evaluatedConstTracker, onWarning, onError, suppressExceptions) {
) : IrConstTransformer(interpreter, irFile, mode, checker, evaluatedConstTracker, onWarning, onError, suppressExceptions) {
protected fun transformAnnotations(annotationContainer: IrAnnotationContainer) {
annotationContainer.annotations.forEach { annotation ->
transformAnnotation(annotation)
@@ -13,20 +13,21 @@ import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclarationBase
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.expressions.IrErrorExpression
import org.jetbrains.kotlin.ir.expressions.IrVararg
import org.jetbrains.kotlin.ir.interpreter.IrInterpreter
import org.jetbrains.kotlin.ir.interpreter.checker.EvaluationMode
import org.jetbrains.kotlin.ir.interpreter.checker.IrInterpreterChecker
import org.jetbrains.kotlin.ir.util.primaryConstructor
internal class IrConstDeclarationAnnotationTransformer(
interpreter: IrInterpreter,
irFile: IrFile,
mode: EvaluationMode,
checker: IrInterpreterChecker,
evaluatedConstTracker: EvaluatedConstTracker?,
onWarning: (IrFile, IrElement, IrErrorExpression) -> Unit,
onError: (IrFile, IrElement, IrErrorExpression) -> Unit,
suppressExceptions: Boolean,
) : IrConstAnnotationTransformer(interpreter, irFile, mode, evaluatedConstTracker, onWarning, onError, suppressExceptions) {
) : IrConstAnnotationTransformer(interpreter, irFile, mode, checker, evaluatedConstTracker, onWarning, onError, suppressExceptions) {
override fun visitFile(declaration: IrFile, data: Nothing?): IrFile {
transformAnnotations(declaration)
return super.visitFile(declaration, data)
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrStringConcatenationImpl
import org.jetbrains.kotlin.ir.interpreter.IrInterpreter
import org.jetbrains.kotlin.ir.interpreter.checker.EvaluationMode
import org.jetbrains.kotlin.ir.interpreter.checker.IrInterpreterChecker
import org.jetbrains.kotlin.ir.interpreter.createGetField
import kotlin.math.max
import kotlin.math.min
@@ -23,11 +24,12 @@ internal class IrConstExpressionTransformer(
interpreter: IrInterpreter,
irFile: IrFile,
mode: EvaluationMode,
checker: IrInterpreterChecker,
evaluatedConstTracker: EvaluatedConstTracker?,
onWarning: (IrFile, IrElement, IrErrorExpression) -> Unit,
onError: (IrFile, IrElement, IrErrorExpression) -> Unit,
suppressExceptions: Boolean,
) : IrConstTransformer(interpreter, irFile, mode, evaluatedConstTracker, onWarning, onError, suppressExceptions) {
) : IrConstTransformer(interpreter, irFile, mode, checker, evaluatedConstTracker, onWarning, onError, suppressExceptions) {
override fun visitCall(expression: IrCall, data: Nothing?): IrElement {
if (expression.canBeInterpreted()) {
return expression.interpret(failAsError = false)
@@ -55,27 +57,31 @@ internal class IrConstExpressionTransformer(
)
fun IrExpression.wrapInToStringConcatAndInterpret(): IrExpression = wrapInStringConcat().interpret(failAsError = false)
fun IrExpression.getConstStringOrEmpty(): String = if (this is IrConst<*>) value.toString() else ""
// If we have some complex expression in arguments (like some `IrComposite`) we will skip it,
// but we must visit this argument in order to apply all possible optimizations.
val transformed = super.visitStringConcatenation(expression, data) as? IrStringConcatenation ?: return expression
// here `StringBuilder`'s list is used to optimize memory, everything works without it
val folded = mutableListOf<IrExpression>()
val buildersList = mutableListOf<StringBuilder>()
for (next in expression.arguments) {
for (next in transformed.arguments) {
val last = folded.lastOrNull()
when {
!next.wrapInStringConcat().canBeInterpreted() -> {
folded += next
buildersList.add(StringBuilder())
buildersList.add(StringBuilder(next.getConstStringOrEmpty()))
}
last == null || !last.wrapInStringConcat().canBeInterpreted() -> {
val result = next.wrapInToStringConcatAndInterpret()
folded += result
buildersList.add(StringBuilder((result as? IrConst<*>)?.value?.toString() ?: ""))
buildersList.add(StringBuilder(result.getConstStringOrEmpty()))
}
else -> {
val nextAsConst = next.wrapInToStringConcatAndInterpret()
if (nextAsConst !is IrConst<*>) {
folded += next
buildersList.add(StringBuilder())
buildersList.add(StringBuilder(next.getConstStringOrEmpty()))
} else {
folded[folded.size - 1] = IrConstImpl.string(
// Inlined strings may have `last.startOffset > next.endOffset`
@@ -16,18 +16,13 @@ import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.interpreter.IrInterpreter
import org.jetbrains.kotlin.ir.interpreter.IrInterpreterConfiguration
import org.jetbrains.kotlin.ir.interpreter.checker.EvaluationMode
import org.jetbrains.kotlin.ir.interpreter.checker.IrInterpreterCheckerData
import org.jetbrains.kotlin.ir.interpreter.checker.IrInterpreterCommonChecker
import org.jetbrains.kotlin.ir.interpreter.checker.IrInterpreterNameChecker
import org.jetbrains.kotlin.ir.interpreter.checker.*
import org.jetbrains.kotlin.ir.interpreter.preprocessor.IrInterpreterKCallableNamePreprocessor
import org.jetbrains.kotlin.ir.interpreter.preprocessor.IrInterpreterPreprocessorData
import org.jetbrains.kotlin.ir.interpreter.toConstantValue
import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
private val preprocessors = setOf(IrInterpreterKCallableNamePreprocessor())
fun IrFile.transformConst(
interpreter: IrInterpreter,
mode: EvaluationMode,
@@ -36,22 +31,30 @@ fun IrFile.transformConst(
onError: (IrFile, IrElement, IrErrorExpression) -> Unit = { _, _, _ -> },
suppressExceptions: Boolean = false,
) {
val preprocessedFile = preprocessors.fold(this) { acc, preprocessor ->
preprocessor.preprocess(acc, IrInterpreterPreprocessorData(mode, interpreter.irBuiltIns))
val preprocessors = setOf(IrInterpreterKCallableNamePreprocessor())
val preprocessedFile = preprocessors.fold(this) { file, preprocessor ->
preprocessor.preprocess(file, IrInterpreterPreprocessorData(mode, interpreter.irBuiltIns))
}
val irConstExpressionTransformer = IrConstExpressionTransformer(
interpreter, preprocessedFile, mode, evaluatedConstTracker, onWarning, onError, suppressExceptions
val checkers = setOf(
IrInterpreterNameChecker(),
IrInterpreterCommonChecker(),
)
val irConstDeclarationAnnotationTransformer = IrConstDeclarationAnnotationTransformer(
interpreter, preprocessedFile, mode, evaluatedConstTracker, onWarning, onError, suppressExceptions
)
val irConstTypeAnnotationTransformer = IrConstTypeAnnotationTransformer(
interpreter, preprocessedFile, mode, evaluatedConstTracker, onWarning, onError, suppressExceptions
)
preprocessedFile.transform(irConstExpressionTransformer, null)
preprocessedFile.transform(irConstDeclarationAnnotationTransformer, null)
preprocessedFile.transform(irConstTypeAnnotationTransformer, null)
checkers.fold(preprocessedFile) { file, checker ->
val irConstExpressionTransformer = IrConstExpressionTransformer(
interpreter, file, mode, checker, evaluatedConstTracker, onWarning, onError, suppressExceptions
)
val irConstDeclarationAnnotationTransformer = IrConstDeclarationAnnotationTransformer(
interpreter, file, mode, checker, evaluatedConstTracker, onWarning, onError, suppressExceptions
)
val irConstTypeAnnotationTransformer = IrConstTypeAnnotationTransformer(
interpreter, file, mode, checker, evaluatedConstTracker, onWarning, onError, suppressExceptions
)
file.transform(irConstExpressionTransformer, null)
file.transform(irConstDeclarationAnnotationTransformer, null)
file.transform(irConstTypeAnnotationTransformer, null)
}
}
// Note: We are using `IrElementTransformer` here instead of `IrElementTransformerVoid` to avoid conflicts with `IrTypeVisitorVoid`
@@ -60,16 +63,12 @@ internal abstract class IrConstTransformer(
protected val interpreter: IrInterpreter,
private val irFile: IrFile,
private val mode: EvaluationMode,
private val checker: IrInterpreterChecker,
private val evaluatedConstTracker: EvaluatedConstTracker? = null,
private val onWarning: (IrFile, IrElement, IrErrorExpression) -> Unit,
private val onError: (IrFile, IrElement, IrErrorExpression) -> Unit,
private val suppressExceptions: Boolean,
) : IrElementTransformer<Nothing?> {
private val checkers = setOf(
IrInterpreterCommonChecker(mode),
IrInterpreterNameChecker(mode)
)
private fun IrExpression.warningIfError(original: IrExpression): IrExpression {
if (this is IrErrorExpression) {
onWarning(irFile, original, this)
@@ -94,7 +93,7 @@ internal abstract class IrConstTransformer(
configuration: IrInterpreterConfiguration = interpreter.environment.configuration
): Boolean {
return try {
checkers.any { this.accept(it, IrInterpreterCheckerData(interpreter.irBuiltIns, configuration)) }
this.accept(checker, IrInterpreterCheckerData(mode, interpreter.irBuiltIns, configuration))
} catch (e: Throwable) {
if (suppressExceptions) {
return false
@@ -7,22 +7,26 @@ package org.jetbrains.kotlin.ir.interpreter.transformer
import org.jetbrains.kotlin.constant.EvaluatedConstTracker
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.expressions.IrErrorExpression
import org.jetbrains.kotlin.ir.interpreter.IrInterpreter
import org.jetbrains.kotlin.ir.interpreter.checker.EvaluationMode
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.interpreter.checker.IrInterpreterChecker
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.typeOrNull
import org.jetbrains.kotlin.ir.visitors.IrTypeTransformerVoid
internal class IrConstTypeAnnotationTransformer(
interpreter: IrInterpreter,
irFile: IrFile,
mode: EvaluationMode,
checker: IrInterpreterChecker,
evaluatedConstTracker: EvaluatedConstTracker?,
onWarning: (IrFile, IrElement, IrErrorExpression) -> Unit,
onError: (IrFile, IrElement, IrErrorExpression) -> Unit,
suppressExceptions: Boolean,
) : IrConstAnnotationTransformer(interpreter, irFile, mode, evaluatedConstTracker, onWarning, onError, suppressExceptions),
) : IrConstAnnotationTransformer(interpreter, irFile, mode, checker, evaluatedConstTracker, onWarning, onError, suppressExceptions),
IrTypeTransformerVoid<Nothing?> {
override fun <Type : IrType?> transformType(container: IrElement, type: Type, data: Nothing?): Type {
@@ -24,9 +24,12 @@ class A {
val temp = A()
val g = temp::b.<!EVALUATED("b")!>name<!>
val insideStringConcat = "${temp::b.<!EVALUATED("b")!>name<!>}"
val complexExpression1 = A()::a.<!EVALUATED("a")!>name<!> + A()::b.<!EVALUATED("b")!>name<!>
val complexExpression2 = A::a.<!EVALUATED("a")!>name<!> <!EVALUATED("ab")!>+ A::b.<!EVALUATED("b")!>name<!><!>
var recursive = ::test.<!EVALUATED("test")!>name<!>
}
fun getA(): A = A()