From c8b268a789eabebb9f4bd0b3e7c369cab2900125 Mon Sep 17 00:00:00 2001 From: Ivan Kylchik Date: Wed, 17 Mar 2021 19:59:53 +0300 Subject: [PATCH] Refactor entire IrInterpreter to achieve better code quality --- .../ir/interpreter/InstructionsUnfolder.kt | 436 +++++++ .../kotlin/ir/interpreter/IrInterpreter.kt | 1027 +++++++---------- .../interpreter/IrInterpreterEnvironment.kt | 44 + .../jetbrains/kotlin/ir/interpreter/Label.kt | 89 -- .../jetbrains/kotlin/ir/interpreter/Utils.kt | 51 +- .../interpreter/exceptions/UserException.kt | 19 - .../intrinsics/IntrinsicEvaluator.kt | 31 +- .../intrinsics/IntrinsicImplementations.kt | 218 ++-- .../ir/interpreter/proxy/CommonProxy.kt | 8 +- .../proxy/reflection/KFunctionProxy.kt | 2 +- .../proxy/reflection/KProperty1Proxy.kt | 6 +- .../proxy/reflection/KProperty2Proxy.kt | 6 +- .../kotlin/ir/interpreter/stack/CallStack.kt | 201 ++++ .../kotlin/ir/interpreter/stack/Frame.kt | 181 ++- .../kotlin/ir/interpreter/stack/Stack.kt | 188 --- .../kotlin/ir/interpreter/state/Common.kt | 3 +- .../ir/interpreter/state/ExceptionState.kt | 4 +- .../kotlin/ir/interpreter/state/State.kt | 10 +- .../ir/interpreter/state/StateWithClosure.kt | 12 + .../state/reflection/KFunctionState.kt | 8 +- 20 files changed, 1459 insertions(+), 1085 deletions(-) create mode 100644 compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/InstructionsUnfolder.kt create mode 100644 compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrInterpreterEnvironment.kt delete mode 100644 compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/Label.kt delete mode 100644 compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/exceptions/UserException.kt create mode 100644 compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/stack/CallStack.kt delete mode 100644 compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/stack/Stack.kt create mode 100644 compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/StateWithClosure.kt 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 new file mode 100644 index 00000000000..6765da62529 --- /dev/null +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/InstructionsUnfolder.kt @@ -0,0 +1,436 @@ +/* + * Copyright 2010-2021 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.ir.interpreter + +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl +import org.jetbrains.kotlin.ir.interpreter.builtins.evaluateIntrinsicAnnotation +import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterError +import org.jetbrains.kotlin.ir.interpreter.stack.CallStack +import org.jetbrains.kotlin.ir.interpreter.stack.Variable +import org.jetbrains.kotlin.ir.interpreter.state.* +import org.jetbrains.kotlin.ir.types.classOrNull +import org.jetbrains.kotlin.ir.types.classifierOrNull +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.ir.util.hasAnnotation + +internal fun unfoldInstruction(element: IrElement?, environment: IrInterpreterEnvironment) { + val callStack = environment.callStack + when (element) { + null -> return + is IrSimpleFunction -> unfoldFunction(element, environment) + is IrConstructor -> unfoldConstructor(element, callStack) + is IrCall -> unfoldCall(element, callStack) + is IrConstructorCall -> unfoldConstructorCall(element, callStack) + is IrEnumConstructorCall -> unfoldEnumConstructorCall(element, callStack) + is IrDelegatingConstructorCall -> unfoldDelegatingConstructorCall(element, callStack) + is IrInstanceInitializerCall -> unfoldInstanceInitializerCall(element, callStack) + is IrField -> unfoldField(element, callStack) + is IrBody -> unfoldBody(element, callStack) + is IrBlock -> unfoldBlock(element, callStack) + is IrReturn -> unfoldReturn(element, callStack) + is IrSetField -> unfoldSetField(element, callStack) + is IrGetField -> callStack.addInstruction(SimpleInstruction(element)) + is IrGetValue -> unfoldGetValue(element, environment) + is IrGetObjectValue -> unfoldGetObjectValue(element, environment) + is IrGetEnumValue -> unfoldGetEnumValue(element, environment) + is IrEnumEntry -> unfoldEnumEntry(element, environment) + is IrConst<*> -> callStack.addInstruction(SimpleInstruction(element)) + is IrVariable -> unfoldVariable(element, callStack) + is IrSetValue -> unfoldSetValue(element, callStack) + is IrTypeOperatorCall -> unfoldTypeOperatorCall(element, callStack) + is IrBranch -> unfoldBranch(element, callStack) + is IrWhileLoop -> unfoldWhileLoop(element, callStack) + is IrDoWhileLoop -> unfoldDoWhileLoop(element, callStack) + is IrWhen -> unfoldWhen(element, callStack) + is IrBreak -> unfoldBreak(element, callStack) + is IrContinue -> unfoldContinue(element, callStack) + is IrVararg -> unfoldVararg(element, callStack) + is IrSpreadElement -> callStack.addInstruction(CompoundInstruction(element.expression)) + is IrTry -> unfoldTry(element, callStack) + is IrCatch -> unfoldCatch(element, callStack) + is IrThrow -> unfoldThrow(element, callStack) + is IrStringConcatenation -> unfoldStringConcatenation(element, environment) + is IrFunctionExpression -> callStack.addInstruction(SimpleInstruction(element)) + is IrFunctionReference -> unfoldFunctionReference(element, callStack) + is IrPropertyReference -> unfoldPropertyReference(element, callStack) + is IrClassReference -> unfoldClassReference(element, callStack) + is IrComposite -> unfoldComposite(element, callStack) + + else -> TODO("${element.javaClass} not supported") + } +} + +private fun unfoldFunction(function: IrSimpleFunction, environment: IrInterpreterEnvironment) { + if (environment.callStack.getStackCount() >= IrInterpreterEnvironment.MAX_STACK) + return StackOverflowError().handleUserException(environment) + // SimpleInstruction with function is added in IrCall + // It will serve as endpoint for all possible calls, there we drop frame and copy result to new one + function.body?.let { environment.callStack.addInstruction(CompoundInstruction(it)) } + ?: throw InterpreterError("Ir function must be with body") +} + +private fun unfoldConstructor(constructor: IrConstructor, callStack: CallStack) { + // SimpleInstruction with function is added in constructor call + // It will serve as endpoint for all possible constructor calls, there we drop frame and return object + callStack.addInstruction(CompoundInstruction(constructor.body!!)) +} + +private fun unfoldCall(call: IrCall, callStack: CallStack) { + val function = call.symbol.owner + // new sub frame is used to store value arguments, in case then they are used in default args evaluation + callStack.newSubFrame(call, listOf()) + callStack.addInstruction(SimpleInstruction(call)) + unfoldValueParameters(call, callStack) + + // must save receivers in memory in case then they are used in default args evaluation + call.extensionReceiver?.let { + callStack.addInstruction(SimpleInstruction(function.extensionReceiverParameter!!)) + callStack.addInstruction(CompoundInstruction(it)) + } + call.dispatchReceiver?.let { + callStack.addInstruction(SimpleInstruction(function.dispatchReceiverParameter!!)) + callStack.addInstruction(CompoundInstruction(it)) + } +} + +private fun unfoldConstructorCall(constructorCall: IrFunctionAccessExpression, callStack: CallStack) { + val constructor = constructorCall.symbol.owner + callStack.newSubFrame(constructorCall, listOf()) // used to store value arguments, in case then they are use as default args + // this variable is used to create object once + callStack.addVariable(Variable(constructorCall.getThisReceiver(), Common(constructor.parentAsClass))) + callStack.addInstruction(SimpleInstruction(constructorCall)) + unfoldValueParameters(constructorCall, callStack) + + constructorCall.dispatchReceiver?.let { + callStack.addInstruction(SimpleInstruction(constructor.dispatchReceiverParameter!!)) + callStack.addInstruction(CompoundInstruction(it)) + } +} + +private fun unfoldEnumConstructorCall(enumConstructorCall: IrEnumConstructorCall, callStack: CallStack) { + callStack.newSubFrame(enumConstructorCall, listOf()) // used to store value arguments, in case then they are use as default args + callStack.addInstruction(SimpleInstruction(enumConstructorCall)) + unfoldValueParameters(enumConstructorCall, callStack) +} + +private fun unfoldDelegatingConstructorCall(delegatingConstructorCall: IrFunctionAccessExpression, callStack: CallStack) { + callStack.newSubFrame(delegatingConstructorCall, listOf()) // used to store value arguments, in case then they are use as default args + callStack.addInstruction(SimpleInstruction(delegatingConstructorCall)) + unfoldValueParameters(delegatingConstructorCall, callStack) +} + +private fun unfoldValueParameters(expression: IrFunctionAccessExpression, callStack: CallStack) { + fun IrValueParameter.getDefault(): IrExpressionBody? { + return defaultValue + ?: (this.parent as? IrSimpleFunction)?.overriddenSymbols + ?.map { it.owner.valueParameters[this.index].getDefault() } + ?.firstNotNullOfOrNull { it } + } + + fun getDefaultForParameterAt(index: Int): IrExpression? { + return expression.symbol.owner.valueParameters[index].getDefault()?.expression + } + + val irFunction = expression.symbol.owner + val valueParametersSymbols = irFunction.valueParameters.map { it.symbol } + val valueArguments = (0 until expression.valueArgumentsCount).map { expression.getValueArgument(it) } + + for (i in valueParametersSymbols.indices.reversed()) { + callStack.addInstruction(SimpleInstruction(valueParametersSymbols[i].owner)) + val arg = valueArguments[i] ?: getDefaultForParameterAt(i) + when { + arg != null -> callStack.addInstruction(CompoundInstruction(arg)) + else -> + // case when value parameter is vararg and it is missing + expression.getVarargType(i)?.let { + callStack.addInstruction(SimpleInstruction(IrConstImpl.constNull(UNDEFINED_OFFSET, UNDEFINED_OFFSET, it))) + } + } + } + + // hack for extension receiver in lambda + if (expression.valueArgumentsCount != irFunction.valueParameters.size) { + val extensionReceiver = irFunction.getExtensionReceiver() ?: return + callStack.addInstruction(SimpleInstruction(extensionReceiver.owner)) + valueArguments[0]?.let { callStack.addInstruction(CompoundInstruction(it)) } + } +} + +private fun unfoldInstanceInitializerCall(instanceInitializerCall: IrInstanceInitializerCall, callStack: CallStack) { + val irClass = instanceInitializerCall.classSymbol.owner + + // init blocks processing + val anonymousInitializer = irClass.declarations.filterIsInstance().filter { !it.isStatic } + anonymousInitializer.reversed().forEach { callStack.addInstruction(CompoundInstruction(it.body)) } + + // properties processing + val classProperties = irClass.declarations.filterIsInstance() + classProperties.filter { it.backingField?.initializer?.expression != null }.reversed() + .forEach { callStack.addInstruction(CompoundInstruction(it.backingField)) } +} + +private fun unfoldField(field: IrField, callStack: CallStack) { + callStack.addInstruction(SimpleInstruction(field)) + callStack.addInstruction(CompoundInstruction(field.initializer?.expression)) +} + +private fun unfoldBody(body: IrBody, callStack: CallStack) { + unfoldStatements(body.statements, callStack) +} + +private fun unfoldBlock(block: IrBlock, callStack: CallStack) { + callStack.newSubFrame(block, listOf()) + callStack.addInstruction(SimpleInstruction(block)) + unfoldStatements(block.statements, callStack) +} + +private fun unfoldStatements(statements: List, callStack: CallStack) { + for (i in statements.indices.reversed()) { + when (val statement = statements[i]) { + is IrClass -> if (!statement.isLocal) TODO("Only local classes are supported") + is IrFunction -> if (!statement.isLocal) TODO("Only local functions are supported") + else -> callStack.addInstruction(CompoundInstruction(statement)) + } + } +} + +private fun unfoldReturn(expression: IrReturn, callStack: CallStack) { + callStack.addInstruction(SimpleInstruction(expression)) //2 + callStack.addInstruction(CompoundInstruction(expression.value)) //1 +} + +private fun unfoldSetField(expression: IrSetField, callStack: CallStack) { + // receiver is null, for example, for top level fields; cannot interpret set on top level var + if (expression.receiver.let { it == null || (it.type.classifierOrNull?.owner as? IrClass)?.isObject == true }) { + error("Cannot interpret set method on top level properties") + } + + callStack.addInstruction(SimpleInstruction(expression)) + callStack.addInstruction(CompoundInstruction(expression.value)) +} + +private fun unfoldGetValue(expression: IrGetValue, environment: IrInterpreterEnvironment) { + val expectedClass = expression.type.classOrNull?.owner + // used to evaluate constants inside object + if (expectedClass != null && expectedClass.isObject && expression.symbol.owner.origin == IrDeclarationOrigin.INSTANCE_RECEIVER) { + // TODO is this correct behaviour? + return unfoldGetObjectValue(IrGetObjectValueImpl(0, 0, expectedClass.defaultType, expectedClass.symbol), environment) + } + environment.callStack.pushState(environment.callStack.getVariable(expression.symbol).state) +} + +private fun unfoldGetObjectValue(expression: IrGetObjectValue, environment: IrInterpreterEnvironment) { + val callStack = environment.callStack + val objectClass = expression.symbol.owner + environment.mapOfObjects[objectClass.symbol]?.let { return callStack.pushState(it) } + + callStack.newSubFrame(expression, listOf(SimpleInstruction(expression))) + when { + !objectClass.hasAnnotation(evaluateIntrinsicAnnotation) -> { + val state = Common(objectClass) + environment.mapOfObjects[objectClass.symbol] = state // must set object's state here to avoid cyclic evaluation + callStack.addVariable(Variable(objectClass.thisReceiver!!.symbol, state)) + + val constructor = objectClass.constructors.first() + val constructorCall = IrConstructorCallImpl.fromSymbolOwner(constructor.returnType, constructor.symbol) + callStack.addInstruction(CompoundInstruction(constructorCall)) + } + } +} + +private fun unfoldGetEnumValue(expression: IrGetEnumValue, environment: IrInterpreterEnvironment) { + val callStack = environment.callStack + environment.mapOfEnums[expression.symbol]?.let { return callStack.pushState(it) } + + callStack.addInstruction(SimpleInstruction(expression)) + val enumEntry = expression.symbol.owner + val enumClass = enumEntry.symbol.owner.parentAsClass + enumClass.declarations.filterIsInstance().forEach { + if (enumClass.hasAnnotation(evaluateIntrinsicAnnotation)) return@forEach callStack.addInstruction(SimpleInstruction(it)) + callStack.addInstruction(CompoundInstruction(it)) + } +} + +private fun unfoldEnumEntry(enumEntry: IrEnumEntry, environment: IrInterpreterEnvironment) { + val enumClass = enumEntry.symbol.owner.parentAsClass + val enumEntries = enumClass.declarations.filterIsInstance() + + val enumSuperCall = (enumClass.primaryConstructor?.body?.statements?.firstOrNull() as? IrEnumConstructorCall) + if (enumEntries.isNotEmpty() && enumSuperCall != null) { + val valueArguments = listOf( + enumEntry.name.asString().toIrConst(environment.irBuiltIns.stringType), + enumEntries.indexOf(enumEntry).toIrConst(environment.irBuiltIns.intType) + ) + valueArguments.forEachIndexed { index, irConst -> enumSuperCall.putValueArgument(index, irConst) } + } + + val enumConstructorCall = enumEntry.initializerExpression?.expression as? IrEnumConstructorCall + ?: throw InterpreterError("Initializer at enum entry ${enumEntry.fqNameWhenAvailable} is null") + val enumClassObject = Variable(enumConstructorCall.getThisReceiver(), Common(enumEntry.correspondingClass ?: enumClass)) + environment.mapOfEnums[enumEntry.symbol] = enumClassObject.state as Complex + + environment.callStack.newSubFrame(enumEntry, listOf(CompoundInstruction(enumConstructorCall), SimpleInstruction(enumEntry))) + environment.callStack.addVariable(enumClassObject) +} + +private fun unfoldVariable(variable: IrVariable, callStack: CallStack) { + when (variable.initializer) { + null -> callStack.addVariable(Variable(variable.symbol)) + else -> { + callStack.addInstruction(SimpleInstruction(variable)) + callStack.addInstruction(CompoundInstruction(variable.initializer!!)) + } + } +} + +private fun unfoldSetValue(expression: IrSetValue, callStack: CallStack) { + callStack.addInstruction(SimpleInstruction(expression)) + callStack.addInstruction(CompoundInstruction(expression.value)) +} + +private fun unfoldTypeOperatorCall(element: IrTypeOperatorCall, callStack: CallStack) { + callStack.addInstruction(SimpleInstruction(element)) + callStack.addInstruction(CompoundInstruction(element.argument)) +} + +private fun unfoldBranch(branch: IrBranch, callStack: CallStack) { + callStack.addInstruction(SimpleInstruction(branch)) //2 + callStack.addInstruction(CompoundInstruction(branch.condition)) //1 +} + +private fun unfoldWhileLoop(loop: IrWhileLoop, callStack: CallStack) { + callStack.newSubFrame(loop, listOf()) + callStack.addInstruction(SimpleInstruction(loop)) + callStack.addInstruction(CompoundInstruction(loop.condition)) +} + +private fun unfoldDoWhileLoop(loop: IrDoWhileLoop, callStack: CallStack) { + callStack.newSubFrame(loop, listOf()) + callStack.addInstruction(SimpleInstruction(loop)) + callStack.addInstruction(CompoundInstruction(loop.condition)) + callStack.addInstruction(CompoundInstruction(loop.body)) +} + +private fun unfoldWhen(element: IrWhen, callStack: CallStack) { + // new sub frame to drop it after + callStack.newSubFrame(element, element.branches.map { CompoundInstruction(it) } + listOf(SimpleInstruction(element))) +} + +private fun unfoldContinue(element: IrContinue, callStack: CallStack) { + callStack.unrollInstructionsForBreakContinue(element) +} + +private fun unfoldBreak(element: IrBreak, callStack: CallStack) { + callStack.unrollInstructionsForBreakContinue(element) +} + +private fun unfoldVararg(element: IrVararg, callStack: CallStack) { + callStack.addInstruction(SimpleInstruction(element)) + element.elements.reversed().forEach { callStack.addInstruction(CompoundInstruction(it)) } +} + +private fun unfoldTry(element: IrTry, callStack: CallStack) { + callStack.newSubFrame(element, listOf()) + callStack.addInstruction(SimpleInstruction(element)) + callStack.addInstruction(CompoundInstruction(element.tryResult)) +} + +private fun unfoldCatch(element: IrCatch, callStack: CallStack) { + val exceptionState = callStack.peekState() as? ExceptionState ?: return + if (exceptionState.isSubtypeOf(element.catchParameter.type)) { + callStack.popState() + val frameOwner = callStack.getCurrentFrameOwner() as IrTry + callStack.dropSubFrame() // drop other catch blocks + callStack.newSubFrame(element, listOf()) // new frame with IrTry instruction to interpret finally block at the end + callStack.addVariable(Variable(element.catchParameter.symbol, exceptionState)) + callStack.addInstruction(SimpleInstruction(frameOwner)) + callStack.addInstruction(CompoundInstruction(element.result)) + } +} + +private fun unfoldThrow(expression: IrThrow, callStack: CallStack) { + callStack.addInstruction(SimpleInstruction(expression)) + callStack.addInstruction(CompoundInstruction(expression.value)) +} + +private fun unfoldStringConcatenation(expression: IrStringConcatenation, environment: IrInterpreterEnvironment) { + val callStack = environment.callStack + callStack.newSubFrame(expression, listOf()) + callStack.addInstruction(SimpleInstruction(expression)) + + // this callback is used to check the need for an explicit toString call + val explicitToStringCheck = fun() { + when (val state = callStack.peekState()) { + is Common -> { + callStack.popState() + // 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.single().state 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(result.toState(environment.irBuiltIns.stringType)) + } + val toStringFun = state.getToStringFunction() + val receiver = toStringFun.dispatchReceiverParameter!! + val toStringCall = IrCallImpl.fromSymbolOwner(0, 0, environment.irBuiltIns.stringType, toStringFun.symbol) + toStringCall.dispatchReceiver = IrConstImpl.constNull(0, 0, receiver.type) // just stub receiver + + callStack.newSubFrame(toStringCall, listOf(SimpleInstruction(toStringCall))) + callStack.pushState(state) + } + } + } + expression.arguments.reversed().forEach { + callStack.addInstruction(CustomInstruction(explicitToStringCheck)) + callStack.addInstruction(CompoundInstruction(it)) + } +} + +private fun unfoldComposite(element: IrComposite, callStack: CallStack) { + when (element.origin) { + IrStatementOrigin.DESTRUCTURING_DECLARATION -> element.statements.reversed() + .forEach { callStack.addInstruction(CompoundInstruction(it)) } + null -> element.statements.reversed() + .forEach { callStack.addInstruction(CompoundInstruction(it)) } // is null for body of do while loop + else -> TODO("${element.origin} not implemented") + } +} + +private fun unfoldFunctionReference(reference: IrFunctionReference, callStack: CallStack) { + val function = reference.symbol.owner + callStack.newSubFrame(reference, listOf()) + callStack.addInstruction(SimpleInstruction(reference)) + + reference.extensionReceiver?.let { + callStack.addInstruction(SimpleInstruction(function.extensionReceiverParameter!!)) + callStack.addInstruction(CompoundInstruction(it)) + } + reference.dispatchReceiver?.let { + callStack.addInstruction(SimpleInstruction(function.dispatchReceiverParameter!!)) + callStack.addInstruction(CompoundInstruction(it)) + } +} + +private fun unfoldPropertyReference(propertyReference: IrPropertyReference, callStack: CallStack) { + callStack.addInstruction(SimpleInstruction(propertyReference)) + callStack.addInstruction(CompoundInstruction(propertyReference.dispatchReceiver)) +} + +private fun unfoldClassReference(classReference: IrClassReference, callStack: CallStack) { + callStack.addInstruction(SimpleInstruction(classReference)) +} \ No newline at end of file diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrInterpreter.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrInterpreter.kt index 0174c5d9b1a..e8157189372 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrInterpreter.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrInterpreter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2021 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. */ @@ -7,51 +7,75 @@ package org.jetbrains.kotlin.ir.interpreter import org.jetbrains.kotlin.builtins.UnsignedType import org.jetbrains.kotlin.ir.IrElement -import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl -import org.jetbrains.kotlin.ir.expressions.impl.IrErrorExpressionImpl -import org.jetbrains.kotlin.ir.interpreter.builtins.* -import org.jetbrains.kotlin.ir.interpreter.exceptions.* +import org.jetbrains.kotlin.ir.interpreter.builtins.evaluateIntrinsicAnnotation +import org.jetbrains.kotlin.ir.interpreter.builtins.interpretBinaryFunction +import org.jetbrains.kotlin.ir.interpreter.builtins.interpretTernaryFunction +import org.jetbrains.kotlin.ir.interpreter.builtins.interpretUnaryFunction +import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterError +import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterTimeOutError import org.jetbrains.kotlin.ir.interpreter.intrinsics.IntrinsicEvaluator import org.jetbrains.kotlin.ir.interpreter.proxy.CommonProxy.Companion.asProxy import org.jetbrains.kotlin.ir.interpreter.proxy.Proxy import org.jetbrains.kotlin.ir.interpreter.proxy.wrap -import org.jetbrains.kotlin.ir.interpreter.stack.StackImpl +import org.jetbrains.kotlin.ir.interpreter.stack.CallStack import org.jetbrains.kotlin.ir.interpreter.stack.Variable import org.jetbrains.kotlin.ir.interpreter.state.* +import org.jetbrains.kotlin.ir.interpreter.state.Complex +import org.jetbrains.kotlin.ir.interpreter.state.ExceptionState +import org.jetbrains.kotlin.ir.interpreter.state.Primitive +import org.jetbrains.kotlin.ir.interpreter.state.State +import org.jetbrains.kotlin.ir.interpreter.state.asBoolean +import org.jetbrains.kotlin.ir.interpreter.state.isSubtypeOf import org.jetbrains.kotlin.ir.interpreter.state.reflection.* -import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.interpreter.state.reflection.KClassState +import org.jetbrains.kotlin.ir.interpreter.state.reflection.KFunctionState +import org.jetbrains.kotlin.ir.interpreter.state.reflection.KPropertyState +import org.jetbrains.kotlin.ir.interpreter.state.reflection.KTypeState +import org.jetbrains.kotlin.ir.interpreter.state.reflection.ReflectionState import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.impl.originalKotlinType import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult import java.lang.invoke.MethodHandle -private const val MAX_COMMANDS = 500_000 -private const val MAX_STACK = 500 +internal interface Instruction { + val element: IrElement? +} -class IrInterpreter(val irBuiltIns: IrBuiltIns, private val bodyMap: Map = emptyMap()) { - private val irExceptions = mutableListOf() +internal class CompoundInstruction(override val element: IrElement?) : Instruction // must unwind first +internal class SimpleInstruction(override val element: IrElement) : Instruction // must interpret as is +internal class CustomInstruction(val evaluate: () -> Unit) : Instruction { + override val element: IrElement? + get() = null +} - private val stack = StackImpl() +class IrInterpreter private constructor( + private val bodyMap: Map, + private val environment: IrInterpreterEnvironment +) { + val irBuiltIns: IrBuiltIns + get() = environment.irBuiltIns + private val callStack: CallStack + get() = environment.callStack private var commandCount = 0 - private val mapOfEnums = mutableMapOf() - private val mapOfObjects = mutableMapOf() + constructor(irBuiltIns: IrBuiltIns, bodyMap: Map = emptyMap()) : + this(bodyMap, IrInterpreterEnvironment(irBuiltIns, CallStack())) - constructor(irModule: IrModuleFragment) : this(irModule.irBuiltins) { - irExceptions.addAll( - irModule.files - .flatMap { it.declarations } - .filterIsInstance() - .filter { it.isSubclassOf(irBuiltIns.throwableClass.owner) } - ) + private constructor(environment: IrInterpreterEnvironment, bodyMap: Map = emptyMap()) : + this(bodyMap, environment) + + constructor(irModule: IrModuleFragment) : this(emptyMap(), IrInterpreterEnvironment(irModule)) + + private fun incrementAndCheckCommands() { + commandCount++ + if (commandCount >= IrInterpreterEnvironment.MAX_COMMANDS) InterpreterTimeOutError().handleUserException(environment) } private fun Any?.getType(defaultType: IrType): IrType { @@ -70,114 +94,114 @@ class IrInterpreter(val irBuiltIns: IrBuiltIns, private val bodyMap: Map= MAX_COMMANDS) InterpreterTimeOutError().throwAsUserException() + private fun getUnitState(): State { + return environment.mapOfObjects.getOrPut(irBuiltIns.unitClass) { Common(irBuiltIns.unitClass.owner) } } - fun interpret(expression: IrExpression, rootFile: IrFile? = null): IrExpression { - stack.clean(rootFile) - return when (val returnLabel = expression.interpret().returnLabel) { - ReturnLabel.REGULAR -> stack.popReturnValue().toIrExpression(expression) - ReturnLabel.EXCEPTION -> { - val message = (stack.popReturnValue() as ExceptionState).getFullDescription() - IrErrorExpressionImpl(expression.startOffset, expression.endOffset, expression.type, "\n" + message) - } - else -> TODO("$returnLabel not supported as result of interpretation") + private fun Instruction.handle() { + when (this) { + is CompoundInstruction -> unfoldInstruction(this.element, environment) + is SimpleInstruction -> interpret(this.element) + is CustomInstruction -> this.evaluate() } } - internal fun IrFunction.interpret(valueArguments: List, expectedResultClass: Class<*> = Any::class.java): Any? { - val returnLabel = stack.newFrame(this, initPool = valueArguments) { - this@interpret.interpret() - } - return when (returnLabel.returnLabel) { - ReturnLabel.REGULAR -> stack.popReturnValue().wrap(this@IrInterpreter, expectedResultClass) - ReturnLabel.EXCEPTION -> throw stack.popReturnValue() as ExceptionState - else -> TODO("${returnLabel::class} not supported as result of interpretation") - } - } + fun interpret(expression: IrExpression, file: IrFile? = null): IrExpression { + commandCount = 0 + callStack.newFrame(expression, listOf(CompoundInstruction(expression)), file) - private fun IrElement.interpret(): ExecutionResult { - try { + while (!callStack.hasNoInstructions()) { + callStack.popInstruction().handle() incrementAndCheckCommands() - val executionResult = when (this) { - is IrSimpleFunction -> interpretFunction(this) - is IrCall -> interpretCall(this) - is IrConstructorCall -> interpretConstructorCall(this) - is IrEnumConstructorCall -> interpretEnumConstructorCall(this) - is IrDelegatingConstructorCall -> interpretDelegatingConstructorCall(this) - is IrInstanceInitializerCall -> interpretInstanceInitializerCall(this) - is IrBody -> interpretBody(this) - is IrBlock -> interpretBlock(this) - is IrReturn -> interpretReturn(this) - is IrSetField -> interpretSetField(this) - is IrGetField -> interpretGetField(this) - is IrGetValue -> interpretGetValue(this) - is IrGetObjectValue -> interpretGetObjectValue(this) - is IrGetEnumValue -> interpretGetEnumValue(this) - is IrEnumEntry -> interpretEnumEntry(this) - is IrConst<*> -> interpretConst(this) - is IrVariable -> interpretVariable(this) - is IrSetValue -> interpretSetVariable(this) - is IrTypeOperatorCall -> interpretTypeOperatorCall(this) - is IrBranch -> interpretBranch(this) - is IrWhileLoop -> interpretWhile(this) - is IrDoWhileLoop -> interpretDoWhile(this) - is IrWhen -> interpretWhen(this) - is IrBreak -> interpretBreak(this) - is IrContinue -> interpretContinue(this) - is IrVararg -> interpretVararg(this) - is IrSpreadElement -> interpretSpreadElement(this) - is IrTry -> interpretTry(this) - is IrCatch -> interpretCatch(this) - is IrThrow -> interpretThrow(this) - is IrStringConcatenation -> interpretStringConcatenation(this) - is IrFunctionExpression -> interpretFunctionExpression(this) - is IrFunctionReference -> interpretFunctionReference(this) - is IrPropertyReference -> interpretPropertyReference(this) - is IrClassReference -> interpretClassReference(this) - is IrComposite -> interpretComposite(this) + } - else -> TODO("${this.javaClass} not supported") - } + return callStack.popState().toIrExpression(expression).apply { callStack.dropFrame() } + } - return executionResult.getNextLabel(this) { this@getNextLabel.interpret() } - } catch (e: UserException) { - // can handle only user exceptions, all others must be rethrown - val exceptionName = e.exception::class.java.simpleName - val irExceptionClass = irExceptions.firstOrNull { it.name.asString() == exceptionName } ?: irBuiltIns.throwableClass.owner - stack.pushReturnValue(ExceptionState(e.exception, irExceptionClass, stack.getStackTrace())) - return Exception + private fun withNewCallStack(block: IrInterpreter.() -> Any?): Any? { + return with(IrInterpreter(environment.copyWithNewCallStack(), bodyMap)) { + block() } } - // this method is used to get stack trace after exception - private fun interpretFunction(irFunction: IrSimpleFunction): ExecutionResult { - if (stack.getStackCount() >= MAX_STACK) StackOverflowError().throwAsUserException() - if (irFunction.body is IrSyntheticBody) return handleIntrinsicMethods(irFunction) - return irFunction.body?.interpret() ?: throw InterpreterError("Ir function must be with body") + internal fun IrFunction.proxyInterpret(valueArguments: List, expectedResultClass: Class<*> = Any::class.java): Any? { + return withNewCallStack { + callStack.newFrame(this@proxyInterpret, listOf(CompoundInstruction(this@proxyInterpret))) + valueArguments.forEach { callStack.addVariable(it) } + + while (!callStack.hasNoInstructions()) { + callStack.popInstruction().handle() + incrementAndCheckCommands() + } + + if (callStack.peekState() == null) callStack.pushState(getUnitState()) // TODO maybe move this logic to body/block + callStack.popState().wrap(this@IrInterpreter, expectedResultClass).apply { callStack.dropFrame() } + } } - private fun MethodHandle?.invokeMethod(irFunction: IrFunction): ExecutionResult { + private fun interpret(element: IrElement) { + when (element) { + is IrSimpleFunction -> interpretFunction(element) + is IrConstructor -> interpretConstructor(element) + is IrCall -> interpretCall(element) + is IrConstructorCall -> interpretConstructorCall(element) + is IrEnumConstructorCall -> interpretEnumConstructorCall(element) + is IrDelegatingConstructorCall -> interpretDelegatingConstructorCall(element) + is IrValueParameter -> interpretValueParameter(element) + is IrInstanceInitializerCall -> interpretInstanceInitializerCall(element) + is IrField -> interpretField(element) + is IrBlock -> callStack.dropSubFrame() + is IrReturn -> interpretReturn(element) + is IrSetField -> interpretSetField(element) + is IrGetField -> interpretGetField(element) + is IrGetObjectValue -> interpretGetObjectValue(element) + is IrGetEnumValue -> interpretGetEnumValue(element) + is IrEnumEntry -> interpretEnumEntry(element) + is IrConst<*> -> interpretConst(element) + is IrVariable -> callStack.addVariable(Variable(element.symbol, callStack.popState())) + is IrSetValue -> callStack.getVariable(element.symbol).state = callStack.popState() + is IrTypeOperatorCall -> interpretTypeOperatorCall(element) + is IrBranch -> interpretBranch(element) + is IrWhileLoop -> interpretWhile(element) + is IrDoWhileLoop -> interpretDoWhile(element) + is IrWhen -> callStack.dropSubFrame() + is IrVararg -> interpretVararg(element) + is IrTry -> interpretTry(element) + is IrThrow -> interpretThrow(element) + is IrStringConcatenation -> interpretStringConcatenation(element) + is IrFunctionExpression -> interpretFunctionExpression(element) + is IrFunctionReference -> interpretFunctionReference(element) + is IrPropertyReference -> interpretPropertyReference(element) + is IrClassReference -> interpretClassReference(element) + else -> TODO("${element.javaClass} not supported for interpretation") + } + } + + private fun interpretFunction(function: IrSimpleFunction) { + function.tryResetFunctionBody() + if (function.checkCast(environment)) { + callStack.dropFrameAndCopyResult() + } + } + + private fun MethodHandle?.invokeMethod(irFunction: IrFunction, args: List) { this ?: return handleIntrinsicMethods(irFunction) - val argsForMethodInvocation = irFunction.getArgsForMethodInvocation(this@IrInterpreter, this.type(), stack.getAll()) - val result = withExceptionHandler { this.invokeWithArguments(argsForMethodInvocation) } - stack.pushReturnValue(result.toState(result.getType(irFunction.returnType))) - - return Next + val argsForMethodInvocation = irFunction.getArgsForMethodInvocation(this@IrInterpreter, this.type(), args) + withExceptionHandler(environment) { + val result = this.invokeWithArguments(argsForMethodInvocation) + callStack.pushState(result.toState(result.getType(irFunction.returnType))) + } } - private fun handleIntrinsicMethods(irFunction: IrFunction): ExecutionResult { - return IntrinsicEvaluator().evaluate(irFunction, stack) { this.interpret() } + private fun handleIntrinsicMethods(irFunction: IrFunction) { + IntrinsicEvaluator.unwindInstructions(irFunction, environment).forEach { callStack.addInstruction(it) } } - private fun calculateBuiltIns(irFunction: IrFunction): ExecutionResult { + private fun calculateBuiltIns(irFunction: IrFunction, args: List) { val methodName = when (val property = (irFunction as? IrSimpleFunction)?.correspondingPropertySymbol) { null -> irFunction.name.asString() else -> property.owner.name.asString() } - val args = stack.getAll().map { it.state } val receiverType = irFunction.dispatchReceiverParameter?.type val argsType = listOfNotNull(receiverType) + irFunction.valueParameters.map { it.type } @@ -192,11 +216,11 @@ class IrInterpreter(val irBuiltIns: IrBuiltIns, private val bodyMap: Map interpretUnaryFunction(methodName, argsType[0].getOnlyName(), argsValues[0]) 2 -> when (methodName) { - "rangeTo" -> return calculateRangeTo(irFunction.returnType) + "rangeTo" -> return calculateRangeTo(irFunction.returnType, args) else -> interpretBinaryFunction( methodName, argsType[0].getOnlyName(), argsType[1].getOnlyName(), argsValues[0], argsValues[1] ) @@ -207,262 +231,215 @@ class IrInterpreter(val irBuiltIns: IrBuiltIns, private val bodyMap: Map throw InterpreterError("Unsupported number of arguments for invocation as builtin functions") } + // TODO check "result is Unit" + callStack.pushState(result.toState(result.getType(irFunction.returnType))) } - // TODO check "result is Unit" - stack.pushReturnValue(result.toState(result.getType(irFunction.returnType))) - return Next } - private fun calculateRangeTo(type: IrType): ExecutionResult { + private fun calculateRangeTo(type: IrType, args: List) { val constructor = type.classOrNull!!.owner.constructors.first() val constructorCall = IrConstructorCallImpl.fromSymbolOwner(constructor.returnType, constructor.symbol) val constructorValueParameters = constructor.valueParameters.map { it.symbol } - val primitiveValueParameters = stack.getAll().map { it.state as Primitive<*> } + val primitiveValueParameters = args.map { it as Primitive<*> } primitiveValueParameters.forEachIndexed { index, primitive -> constructorCall.putValueArgument(index, primitive.value.toIrConst(constructorValueParameters[index].owner.type)) } - return stack.newFrame(initPool = constructorValueParameters.zip(primitiveValueParameters).map { Variable(it.first, it.second) }) { - constructorCall.interpret() - } + callStack.addInstruction(CompoundInstruction(constructorCall)) } - private fun interpretValueParameters( - expression: IrFunctionAccessExpression, irFunction: IrFunction, pool: MutableList - ): ExecutionResult { - // if irFunction is lambda and it has receiver, then first descriptor must be taken from extension receiver - val receiverAsFirstArgument = when (expression.valueArgumentsCount != irFunction.valueParameters.size) { - true -> listOfNotNull(irFunction.getExtensionReceiver()) - else -> listOf() - } - val valueParametersSymbols = receiverAsFirstArgument + irFunction.valueParameters.map { it.symbol } + private fun interpretValueParameter(valueParameter: IrValueParameter) { + val irFunction = valueParameter.parent as IrFunction + fun isReceiver() = irFunction.dispatchReceiverParameter == valueParameter || irFunction.extensionReceiverParameter == valueParameter - fun IrValueParameter.getDefault(): IrExpressionBody? { - return defaultValue - ?: (this.parent as? IrSimpleFunction)?.overriddenSymbols - ?.map { it.owner.valueParameters[this.index].getDefault() } - ?.firstNotNullResult { it } + val result = callStack.popState() + val state = when { + // if vararg is empty + result.isNull() && valueParameter.isVararg -> listOf().toPrimitiveStateArray((result as Primitive<*>).type) + else -> result } - val valueArguments = (0 until expression.valueArgumentsCount).map { expression.getValueArgument(it) } - val defaultValues = irFunction.valueParameters.map { expression.symbol.owner.valueParameters[it.index].getDefault()?.expression } + state.checkNullability(valueParameter.type, environment) { + if (isReceiver()) return@checkNullability NullPointerException() + val method = irFunction.getCapitalizedFileName() + "." + irFunction.fqNameWhenAvailable + val parameter = valueParameter.name + IllegalArgumentException("Parameter specified as non-null is null: method $method, parameter $parameter") + } ?: return - return stack.newFrame(asSubFrame = true, initPool = pool) { - for (i in valueArguments.indices) { - (valueArguments[i] ?: defaultValues[i])?.interpret()?.check { return@newFrame it } - ?: stack.pushReturnValue(listOf().toPrimitiveStateArray(expression.getVarargType(i)!!)) // if vararg is empty - - stack.peekReturnValue().checkNullability(valueParametersSymbols[i].owner.type) { - val method = irFunction.getCapitalizedFileName() + "." + irFunction.fqNameWhenAvailable - val parameter = valueParametersSymbols[i].owner.name - IllegalArgumentException("Parameter specified as non-null is null: method $method, parameter $parameter").throwAsUserException() - } - - with(Variable(valueParametersSymbols[i], stack.popReturnValue())) { - stack.addVar(this) //must add value argument in current stack because it can be used later as default argument - pool.add(this) - } - } - Next - } + //must add value argument in current stack because it can be used later as default argument + callStack.addVariable(Variable(valueParameter.symbol, state)) + callStack.pushState(state) } - private fun interpretCall(expression: IrCall): ExecutionResult { - stack.fixCallEntryPoint(expression) - val valueArguments = mutableListOf() - // dispatch receiver processing - val rawDispatchReceiver = expression.dispatchReceiver - rawDispatchReceiver?.interpret()?.check { return it } - var dispatchReceiver = rawDispatchReceiver?.let { stack.popReturnValue() }?.checkNullability(expression.dispatchReceiver?.type) + private fun interpretCall(call: IrCall) { + // 1. load evaluated arguments from stack + val valueArguments = call.symbol.owner.valueParameters.map { callStack.popState() }.reversed() + val extensionReceiver = call.extensionReceiver?.let { callStack.popState() } + var dispatchReceiver = call.dispatchReceiver?.let { callStack.popState() } - // extension receiver processing - val rawExtensionReceiver = expression.extensionReceiver - rawExtensionReceiver?.interpret()?.check { return it } - val extensionReceiver = rawExtensionReceiver?.let { stack.popReturnValue() }?.checkNullability(expression.extensionReceiver?.type) - - val irFunction = dispatchReceiver?.getIrFunctionByIrCall(expression) ?: expression.symbol.owner + // 2. get correct function for interpretation + val irFunction = dispatchReceiver?.getIrFunctionByIrCall(call) ?: call.symbol.owner dispatchReceiver = when (irFunction.parent) { (dispatchReceiver as? Complex)?.superWrapperClass?.irClass -> dispatchReceiver.superWrapperClass else -> dispatchReceiver } - // it is important firstly to add receiver, then arguments; this order is used in builtin method call - irFunction.getDispatchReceiver()?.let { dispatchReceiver?.let { receiver -> valueArguments.add(Variable(it, receiver)) } } - irFunction.getExtensionReceiver()?.let { extensionReceiver?.let { receiver -> valueArguments.add(Variable(it, receiver)) } } + callStack.dropSubFrame() + callStack.newFrame(irFunction, listOf(SimpleInstruction(irFunction))) + // TODO: if using KTypeState then it's class must be corresponding + // `call.type` is used in check cast and emptyArray + callStack.addVariable(Variable(irFunction.symbol, KTypeState(call.type, irBuiltIns.anyClass.owner))) - interpretValueParameters(expression, irFunction, valueArguments).check { return it } + // 3. store arguments in memory + val args = mutableListOf() + irFunction.getDispatchReceiver()?.let { dispatchReceiver?.let { receiver -> args.add(Variable(it, receiver)) } } + irFunction.getExtensionReceiver()?.let { args.add(Variable(it, extensionReceiver ?: valueArguments.first())) } + // `shift` is used when extension receiver is actually a parameter in lambda + val shift = if (irFunction.extensionReceiverParameter != null && extensionReceiver == null) 1 else 0 + irFunction.valueParameters.forEachIndexed { i, param -> args.add(Variable(param.symbol, valueArguments[i + shift])) } + args.forEach { callStack.addVariable(it) } - irFunction.typeParameters - .filter { - it.isReified || irFunction.fqNameWhenAvailable.toString().let { it == "kotlin.emptyArray" || it == "kotlin.ArrayIntrinsicsKt.emptyArray" } - } - .forEach { - // TODO: emptyArray check is a hack for js, because in js-ir its type parameter isn't marked as reified - // TODO: if using KTypeState then it's class must be corresponding - valueArguments.add(Variable(it.symbol, KTypeState(expression.getTypeArgument(it.index)!!, irBuiltIns.anyClass.owner))) - } + // 4. store reified type parameters + irFunction.typeParameters.filter { it.isReified } + .forEach { callStack.addVariable(Variable(it.symbol, KTypeState(call.getTypeArgument(it.index)!!, irBuiltIns.anyClass.owner))) } - if (dispatchReceiver?.irClass?.isLocal == true || irFunction.isLocal) { - valueArguments.addAll(dispatchReceiver.extractNonLocalDeclarations()) - } + // 5. load up values onto stack + if (dispatchReceiver == null && extensionReceiver == null && irFunction.isLocal) callStack.copyUpValuesFromPreviousFrame() + if (dispatchReceiver is StateWithClosure) callStack.loadUpValues(dispatchReceiver) + if (extensionReceiver is StateWithClosure) callStack.loadUpValues(extensionReceiver) + // 6. load outer class object if (dispatchReceiver is Complex && irFunction.parentClassOrNull?.isInner == true) { - generateSequence(dispatchReceiver.outerClass) { (it.state as? Complex)?.outerClass }.forEach { valueArguments.add(it) } + generateSequence(dispatchReceiver.outerClass) { (it.state as? Complex)?.outerClass }.forEach { callStack.addVariable(it) } } - return stack.newFrame(irFunction, initPool = valueArguments) { - // inline only methods are not presented in lookup table, so must be interpreted instead of execution - val isInlineOnly = irFunction.hasAnnotation(FqName("kotlin.internal.InlineOnly")) - return@newFrame when { - dispatchReceiver is Wrapper && !isInlineOnly -> dispatchReceiver.getMethod(irFunction).invokeMethod(irFunction) - irFunction.hasAnnotation(evaluateIntrinsicAnnotation) -> Wrapper.getStaticMethod(irFunction).invokeMethod(irFunction) - dispatchReceiver is KFunctionState && expression.symbol.owner.name.asString() == "invoke" -> irFunction.interpret() - dispatchReceiver is ReflectionState -> Wrapper.getReflectionMethod(irFunction).invokeMethod(irFunction) - dispatchReceiver is Primitive<*> -> calculateBuiltIns(irFunction) // 'is Primitive' check for js char and js long - irFunction.body == null -> - irFunction.trySubstituteFunctionBody() ?: irFunction.tryCalculateLazyConst() ?: calculateBuiltIns(irFunction) - else -> irFunction.interpret() - } - }.check { return it }.implicitCastIfNeeded(expression.type, irFunction.returnType, stack) + val states = args.map { it.state } + // inline only methods are not presented in lookup table, so must be interpreted instead of execution + val isInlineOnly = irFunction.hasAnnotation(FqName("kotlin.internal.InlineOnly")) + when { + dispatchReceiver is Wrapper && !isInlineOnly -> dispatchReceiver.getMethod(irFunction).invokeMethod(irFunction, states) + irFunction.hasAnnotation(evaluateIntrinsicAnnotation) -> Wrapper.getStaticMethod(irFunction).invokeMethod(irFunction, states) + dispatchReceiver is KFunctionState && call.symbol.owner.name.asString() == "invoke" -> callStack.addInstruction(CompoundInstruction(irFunction)) + dispatchReceiver is ReflectionState -> Wrapper.getReflectionMethod(irFunction).invokeMethod(irFunction, states) + dispatchReceiver is Primitive<*> -> calculateBuiltIns(irFunction, states) // 'is Primitive' check for js char, js long and get field for primitives + irFunction.body is IrSyntheticBody -> handleIntrinsicMethods(irFunction) + irFunction.body == null -> + irFunction.trySubstituteFunctionBody() ?: irFunction.tryCalculateLazyConst() ?: calculateBuiltIns(irFunction, states) + else -> callStack.addInstruction(CompoundInstruction(irFunction)) + } } - private fun IrFunction.trySubstituteFunctionBody(): ExecutionResult? { + private fun IrFunction.trySubstituteFunctionBody(): IrElement? { val signature = this.symbol.signature ?: return null - val body = bodyMap[signature] + this.body = bodyMap[signature] ?: return null + callStack.addInstruction(CompoundInstruction(this)) + return body + } - return body?.let { - try { - this.body = it - this.interpret() - } finally { - this.body = null - } - } + private fun IrFunction.tryResetFunctionBody() { + val signature = this.symbol.signature ?: return + if (bodyMap[signature] != null) this.body = null } // TODO fix in FIR2IR; const val getter must have body with IrGetField node - private fun IrFunction.tryCalculateLazyConst(): ExecutionResult? { + private fun IrFunction.tryCalculateLazyConst(): IrExpression? { if (this !is IrSimpleFunction) return null - return this.correspondingPropertySymbol?.owner?.backingField?.initializer?.interpret() + val expression = this.correspondingPropertySymbol?.owner?.backingField?.initializer?.expression + return expression?.apply { callStack.addInstruction(CompoundInstruction(this)) } } - private fun interpretInstanceInitializerCall(call: IrInstanceInitializerCall): ExecutionResult { + private fun interpretInstanceInitializerCall(call: IrInstanceInitializerCall) { val irClass = call.classSymbol.owner - // properties processing val classProperties = irClass.declarations.filterIsInstance() classProperties.forEach { property -> - property.backingField?.initializer?.expression?.interpret()?.check { return it } + property.backingField?.initializer?.expression ?: return@forEach val receiver = irClass.thisReceiver!!.symbol if (property.backingField?.initializer != null) { - val receiverState = stack.getVariable(receiver).state - val propertyVar = Variable(property.symbol, stack.popReturnValue()) + val receiverState = callStack.getVariable(receiver).state + val propertyVar = Variable(property.symbol, callStack.popState()) receiverState.setField(propertyVar) } } - - // init blocks processing - val anonymousInitializer = irClass.declarations.filterIsInstance().filter { !it.isStatic } - anonymousInitializer.forEach { init -> init.body.interpret().check { return it } } - - return Next } - private fun interpretConstructor(constructorCall: IrFunctionAccessExpression): ExecutionResult { - val owner = constructorCall.symbol.owner - val valueArguments = mutableListOf() + private fun interpretField(field: IrField) { + val irClass = field.parentAsClass + val receiver = irClass.thisReceiver!!.symbol + val receiverState = callStack.getVariable(receiver).state + receiverState.setField(Variable(field.correspondingPropertySymbol!!, callStack.popState())) + } - interpretValueParameters(constructorCall, owner, valueArguments).check { return it } + @Suppress("UNUSED_PARAMETER") + private fun interpretConstructor(constructor: IrConstructor) { + callStack.dropFrameAndCopyResult() + } - val irClass = owner.parent as IrClass - if (irClass.hasAnnotation(evaluateIntrinsicAnnotation) || irClass.fqNameWhenAvailable!!.startsWith(Name.identifier("java"))) { - return stack.newFrame(owner, initPool = valueArguments) { Wrapper.getConstructorMethod(owner).invokeMethod(owner) } - } + private fun interpretConstructorCall(constructorCall: IrFunctionAccessExpression) { + val valueArguments = constructorCall.symbol.owner.valueParameters.map { callStack.popState() }.reversed() + val constructor = constructorCall.symbol.owner + val irClass = constructor.parentAsClass + val objectVar = callStack.getVariable(constructorCall.getThisReceiver()) + if (irClass.isLocal) callStack.storeUpValues(objectVar.state as StateWithClosure) + val outerClass = if (irClass.isInner) callStack.popState() else null - if (irClass.defaultType.isArray() || irClass.defaultType.isPrimitiveArray()) { - // array constructor doesn't have body so must be treated separately - return stack.newFrame(owner, initPool = valueArguments) { handleIntrinsicMethods(owner) } - .apply { - val array = stack.popReturnValue() as Primitive<*> - stack.pushReturnValue(Primitive(array.value, constructorCall.type)) - } - } + callStack.dropSubFrame() // TODO check that data stack is empty + callStack.newFrame(constructor, listOf(SimpleInstruction(constructor))) + callStack.addVariable(objectVar) + constructor.valueParameters.forEachIndexed { i, param -> callStack.addVariable(Variable(param.symbol, valueArguments[i])) } + if (irClass.isLocal) callStack.loadUpValues(objectVar.state as StateWithClosure) - if (irClass.defaultType.isUnsignedType() && valueArguments.size == 1 && owner.valueParameters.size == 1) { - val result = Common(irClass) - result.fields.add(valueArguments.single()) - stack.pushReturnValue(result) - return Next - } - - val state = stack.getVariable(constructorCall.getThisReceiver()).state as Common - - if (irClass.isLocal) { - state.fields.addAll(stack.getAll()) // TODO save only necessary declarations - valueArguments.addAll(stack.getAll()) - } - - if (irClass.isInner) { - constructorCall.dispatchReceiver!!.interpret().check { return it } - state.outerClass = Variable(irClass.parentAsClass.thisReceiver!!.symbol, stack.popReturnValue()) + if (outerClass != null) { + val outerClassVar = Variable(irClass.parentAsClass.thisReceiver!!.symbol, outerClass) + (objectVar.state as Complex).outerClass = outerClassVar // used in case when inner class has inner super class - valueArguments.add(Variable(owner.dispatchReceiverParameter!!.symbol, state.outerClass!!.state)) + callStack.addVariable(Variable(constructor.dispatchReceiverParameter!!.symbol, outerClass)) // used to get information from outer class - valueArguments.add(state.outerClass!!) + callStack.addVariable(outerClassVar) } - valueArguments.add(Variable(constructorCall.getThisReceiver(), state)) //used to set up fields in body - return stack.newFrame(owner, initPool = valueArguments) { - val statements = constructorCall.getBody()!!.statements - when (val irStatement = statements[0]) { - is IrTypeOperatorCall -> { - // enum entry use IrTypeOperatorCall with IMPLICIT_COERCION_TO_UNIT as delegation call, but we need the value - stack.addVar(Variable((irStatement.argument as IrFunctionAccessExpression).getThisReceiver(), state)) - irStatement.argument.interpret().check { return@newFrame it } - } - is IrFunctionAccessExpression -> { - stack.addVar(Variable(irStatement.getThisReceiver(), state)) - irStatement.interpret().check { return@newFrame it } - } - is IrBlock -> { - stack.addVar(Variable((irStatement.statements.last() as IrFunctionAccessExpression).getThisReceiver(), state)) - irStatement.interpret().check { return@newFrame it } - } - else -> TODO("${irStatement::class.java} is not supported as first statement in constructor call") + val superReceiver = when (val irStatement = constructor.body?.statements?.get(0)) { + null -> null // for jvm + is IrTypeOperatorCall -> (irStatement.argument as IrFunctionAccessExpression).getThisReceiver() // for enums + is IrFunctionAccessExpression -> irStatement.getThisReceiver() + is IrBlock -> (irStatement.statements.last() as IrFunctionAccessExpression).getThisReceiver() + else -> TODO("${irStatement::class.java} is not supported as first statement in constructor call") + } + superReceiver?.let { callStack.addVariable(Variable(it, objectVar.state)) } + + when { + irClass.hasAnnotation(evaluateIntrinsicAnnotation) || irClass.fqNameWhenAvailable!!.startsWith(Name.identifier("java")) -> { + Wrapper.getConstructorMethod(constructor).invokeMethod(constructor, valueArguments) + if (constructorCall !is IrConstructorCall) (objectVar.state as Common).superWrapperClass = callStack.popState() as Wrapper + } + irClass.defaultType.isArray() || irClass.defaultType.isPrimitiveArray() -> { + // array constructor doesn't have body so must be treated separately + callStack.addVariable(Variable(constructor.symbol, KTypeState(constructorCall.type, irBuiltIns.anyClass.owner))) + handleIntrinsicMethods(constructor) + } + irClass.defaultType.isUnsignedType() && valueArguments.size == 1 && constructor.valueParameters.size == 1 -> { + callStack.pushState(objectVar.state.apply { fields.add(valueArguments.single()) }) + callStack.addInstruction(SimpleInstruction(constructor)) + } + else -> { + callStack.pushState(objectVar.state) + callStack.addInstruction(CompoundInstruction(constructor)) } - val returnedState = stack.popReturnValue() as Complex - - for (i in 1 until statements.size) statements[i].interpret().check { return@newFrame it } - - state.superWrapperClass = returnedState.superWrapperClass ?: returnedState as? Wrapper - stack.pushReturnValue(state) - Next - } - } - - private fun interpretConstructorCall(constructorCall: IrConstructorCall): ExecutionResult { - val irClass = constructorCall.symbol.owner.parentAsClass - val classState = Variable(constructorCall.getThisReceiver(), Common(irClass)) - return stack.newFrame(asSubFrame = true, initPool = listOf(classState)) { interpretConstructor(constructorCall) } - } - - private fun interpretEnumConstructorCall(enumConstructorCall: IrEnumConstructorCall): ExecutionResult { - // interpretConstructorCall logic is implemented in interpretEnumEntry - return interpretConstructor(enumConstructorCall) - } - - private fun interpretDelegatingConstructorCall(delegatingConstructorCall: IrDelegatingConstructorCall): ExecutionResult { - if (delegatingConstructorCall.symbol.owner.parent == irBuiltIns.anyClass.owner) { - val anyAsStateObject = Common(irBuiltIns.anyClass.owner) - stack.pushReturnValue(anyAsStateObject) - return Next } - return interpretConstructor(delegatingConstructorCall) } - private fun interpretConst(expression: IrConst<*>): ExecutionResult { + private fun interpretDelegatingConstructorCall(constructorCall: IrDelegatingConstructorCall) { + if (constructorCall.symbol.owner.parent == irBuiltIns.anyClass.owner) return callStack.dropSubFrame() + interpretConstructorCall(constructorCall) + } + + private fun interpretEnumConstructorCall(constructorCall: IrEnumConstructorCall) { + interpretConstructorCall(constructorCall) + } + + private fun interpretConst(expression: IrConst<*>) { fun getSignedType(unsignedType: IrType): IrType? = when (unsignedType.getUnsignedType()) { UnsignedType.UBYTE -> irBuiltIns.byteType UnsignedType.USHORT -> irBuiltIns.shortType @@ -472,277 +449,161 @@ class IrInterpreter(val irBuiltIns: IrBuiltIns, private val bodyMap: Map): ExecutionResult { - var executionResult: ExecutionResult = Next - for (statement in statements) { - when (statement) { - is IrClass -> if (statement.isLocal) Next else TODO("Only local classes are supported") - is IrFunction -> if (statement.isLocal) Next else TODO("Only local functions are supported") - else -> executionResult = statement.interpret().check { return it } - } - } - return executionResult - } - - private fun List.withUnitIfNoResult(): ExecutionResult { - return stack.newFrame(asSubFrame = true) { - val executionResult = interpretStatements(this).check { return@newFrame it } - when { - !stack.hasReturnValue() -> getOrCreateObjectValue(irBuiltIns.unitClass.owner) - else -> executionResult - } + private fun interpretWhile(loop: IrWhileLoop) { + val result = callStack.popState().asBoolean() + callStack.dropSubFrame() + if (result) { + callStack.newSubFrame( + loop, + listOf(CompoundInstruction(loop.body), CompoundInstruction(loop.condition), SimpleInstruction(loop)) + ) } } - private fun interpretBlock(block: IrBlock): ExecutionResult { - return block.statements.withUnitIfNoResult() - } - - private fun interpretBody(body: IrBody): ExecutionResult { - return body.statements.withUnitIfNoResult() - } - - private fun interpretReturn(expression: IrReturn): ExecutionResult { - expression.value.interpret().check { return it } - return Return.addOwnerInfo(expression.returnTargetSymbol.owner) - } - - private fun interpretWhile(expression: IrWhileLoop): ExecutionResult { - while (true) { - expression.condition.interpret().check { return it } - if (stack.popReturnValue().asBooleanOrNull() != true) break - expression.body?.interpret()?.check { return it } + private fun interpretDoWhile(loop: IrDoWhileLoop) { + val result = callStack.popState().asBoolean() + callStack.dropSubFrame() + if (result) { + callStack.newSubFrame( + loop, + listOf(CompoundInstruction(loop.body), CompoundInstruction(loop.condition), SimpleInstruction(loop)) + ) } - return Next } - private fun interpretDoWhile(expression: IrDoWhileLoop): ExecutionResult { - do { - // pool from body must be seen to condition expression, so must create temp frame here - stack.newFrame(asSubFrame = true) { - expression.body?.interpret()?.check(ReturnLabel.REGULAR, ReturnLabel.CONTINUE) { return@newFrame it } - expression.condition.interpret().check { return@newFrame it } - Next - }.check { return it } - - if (stack.popReturnValue().asBooleanOrNull() != true) break - } while (true) - return Next - } - - private fun interpretWhen(expression: IrWhen): ExecutionResult { - var executionResult: ExecutionResult = Next - for (branch in expression.branches) { - executionResult = branch.interpret().check { return it } + private fun interpretBranch(branch: IrBranch) { + val result = callStack.popState().asBoolean() + if (result) { + callStack.dropSubFrame() + callStack.addInstruction(CompoundInstruction(branch.result)) } - return executionResult } - private fun interpretBranch(expression: IrBranch): ExecutionResult { - val executionResult = expression.condition.interpret().check { return it } - if (stack.popReturnValue().asBooleanOrNull() == true) { - expression.result.interpret().check { return it } - return BreakWhen - } - return executionResult - } - - private fun interpretBreak(breakStatement: IrBreak): ExecutionResult { - return BreakLoop.addOwnerInfo(breakStatement.loop) - } - - private fun interpretContinue(continueStatement: IrContinue): ExecutionResult { - return Continue.addOwnerInfo(continueStatement.loop) - } - - private fun interpretSetField(expression: IrSetField): ExecutionResult { - // receiver is null, for example, for top level fields; cannot interpret set on top level var - if (expression.receiver.let { it == null || (it.type.classifierOrNull?.owner as? IrClass)?.isObject == true }) { - error("Cannot interpret set method on top level properties") - } - - expression.value.interpret().check { return it } + private fun interpretSetField(expression: IrSetField) { val receiver = (expression.receiver as IrDeclarationReference).symbol val propertySymbol = expression.symbol.owner.correspondingPropertySymbol!! - stack.getVariable(receiver).apply { this.state.setField(Variable(propertySymbol, stack.popReturnValue())) } - return Next + callStack.getVariable(receiver).apply { this.state.setField(Variable(propertySymbol, callStack.popState())) } } - private fun interpretGetField(expression: IrGetField): ExecutionResult { + private fun interpretGetField(expression: IrGetField) { val receiver = (expression.receiver as? IrDeclarationReference)?.symbol val field = expression.symbol.owner // for java static variables - if (field.origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB && field.isStatic) { - val initializerExpression = field.initializer?.expression - if (initializerExpression is IrConst<*>) { - return interpretConst(initializerExpression) + when { + field.origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB && field.isStatic -> { + when (val initializerExpression = field.initializer?.expression) { + is IrConst<*> -> callStack.addInstruction(SimpleInstruction(initializerExpression)) + else -> callStack.pushState(Wrapper.getStaticGetter(field)!!.invokeWithArguments().toState(field.type)) + } + } + field.origin == IrDeclarationOrigin.PROPERTY_BACKING_FIELD && field.correspondingPropertySymbol?.owner?.isConst == true -> { + callStack.addInstruction(CompoundInstruction(field.initializer?.expression)) + } + // receiver is null, for example, for top level fields + expression.receiver.let { it == null || (it.type.classifierOrNull?.owner as? IrClass)?.isObject == true } -> { + val propertyOwner = field.correspondingPropertySymbol?.owner + val isConst = propertyOwner?.isConst == true || propertyOwner?.backingField?.initializer?.expression is IrConst<*> + assert(isConst) { "Cannot interpret get method on top level non const properties" } + callStack.addInstruction(CompoundInstruction(expression.symbol.owner.initializer?.expression)) + } + else -> { + val result = callStack.getVariable(receiver!!).state.getState(field.correspondingPropertySymbol!!) + callStack.pushState(result!!) } - stack.pushReturnValue(Wrapper.getStaticGetter(field)!!.invokeWithArguments().toState(field.type)) - return Next } - if (field.origin == IrDeclarationOrigin.PROPERTY_BACKING_FIELD && field.correspondingPropertySymbol?.owner?.isConst == true) { - return field.initializer?.expression?.interpret() ?: Next - } - // receiver is null, for example, for top level fields - if (expression.receiver.let { it == null || (it.type.classifierOrNull?.owner as? IrClass)?.isObject == true }) { - val propertyOwner = field.correspondingPropertySymbol?.owner - val isConst = propertyOwner?.isConst == true || propertyOwner?.backingField?.initializer?.expression is IrConst<*> - assert(isConst) { "Cannot interpret get method on top level non const properties" } - } - val result = receiver?.let { stack.getVariable(it).state.getState(field.correspondingPropertySymbol!!) } - ?: return (expression.symbol.owner.initializer?.expression?.interpret() ?: Next) - stack.pushReturnValue(result) - return Next } - private fun interpretGetValue(expression: IrGetValue): ExecutionResult { - val expectedClass = expression.type.classOrNull?.owner - // used to evaluate constants inside object - if (expectedClass != null && expectedClass.isObject && expression.symbol.owner.origin == IrDeclarationOrigin.INSTANCE_RECEIVER) { - // TODO is this correct behaviour? - return getOrCreateObjectValue(expectedClass) + private fun interpretGetObjectValue(expression: IrGetObjectValue) { + callStack.dropSubFrame() + + val objectClass = expression.symbol.owner + if (objectClass.hasAnnotation(evaluateIntrinsicAnnotation)) { + val result = Wrapper.getCompanionObject(objectClass) + environment.mapOfObjects[expression.symbol] = result + callStack.pushState(result) } - stack.pushReturnValue(stack.getVariable(expression.symbol).state) - return Next } - private fun interpretVariable(declaration: IrVariable): ExecutionResult { - if (declaration.initializer == null) { - return Next.apply { stack.addVar(Variable(declaration.symbol)) } - } - declaration.initializer?.interpret()?.check { return it } //?: return Next - stack.addVar(Variable(declaration.symbol, stack.popReturnValue())) - return Next + private fun interpretGetEnumValue(expression: IrGetEnumValue) { + callStack.pushState(environment.mapOfEnums[expression.symbol]!!) } - private fun interpretSetVariable(expression: IrSetValue): ExecutionResult { - expression.value.interpret().check { return it } - stack.getVariable(expression.symbol).apply { this.state = stack.popReturnValue() } - return Next - } - - private fun interpretGetObjectValue(expression: IrGetObjectValue): ExecutionResult { - return getOrCreateObjectValue(expression.symbol.owner) - } - - private fun getOrCreateObjectValue(objectClass: IrClass): ExecutionResult { - mapOfObjects[objectClass.symbol]?.let { return Next.apply { stack.pushReturnValue(it) } } + private fun interpretEnumEntry(enumEntry: IrEnumEntry) { + val enumClass = enumEntry.symbol.owner.parentAsClass when { - objectClass.hasAnnotation(evaluateIntrinsicAnnotation) -> mapOfObjects[objectClass.symbol] = Wrapper.getCompanionObject(objectClass) + enumClass.hasAnnotation(evaluateIntrinsicAnnotation) -> { + val enumEntryName = enumEntry.name.asString().toState(environment.irBuiltIns.stringType) + val valueOfFun = enumClass.declarations.single { it.nameForIrSerialization.asString() == "valueOf" } as IrFunction + Wrapper.getEnumEntry(enumClass)!!.invokeMethod(valueOfFun, listOf(enumEntryName)) + environment.mapOfEnums[enumEntry.symbol] = callStack.popState() as Complex + } else -> { - val state = Common(objectClass) - mapOfObjects[objectClass.symbol] = state // TODO test type arguments - - val variable = Variable(objectClass.thisReceiver!!.symbol, state) - val constructor = objectClass.constructors.first() - val constructorCall = IrConstructorCallImpl.fromSymbolOwner(constructor.returnType, constructor.symbol) - stack.newFrame(asSubFrame = true, initPool = listOf(variable)) { interpretConstructor(constructorCall) }.check { return it } + val enumSuperCall = (enumClass.primaryConstructor?.body?.statements?.firstOrNull() as? IrEnumConstructorCall) + enumSuperCall?.apply { (0 until this.valueArgumentsCount).forEach { putValueArgument(it, null) } } // restore to null + callStack.dropSubFrame() } } - stack.pushReturnValue(mapOfObjects[objectClass.symbol]!!) - return Next } - private fun interpretGetEnumValue(expression: IrGetEnumValue): ExecutionResult { - mapOfEnums[expression.symbol]?.let { return Next.apply { stack.pushReturnValue(it) } } - - val enumEntry = expression.symbol.owner - val enumClass = enumEntry.symbol.owner.parentAsClass - val valueOfFun = enumClass.declarations.single { it.nameForIrSerialization.asString() == "valueOf" } as IrFunction - enumClass.declarations.filterIsInstance().forEach { - val executionResult = when { - enumClass.hasAnnotation(evaluateIntrinsicAnnotation) -> { - val enumEntryName = it.name.asString().toState(irBuiltIns.stringType) - val enumNameAsVariable = Variable(valueOfFun.valueParameters.first().symbol, enumEntryName) - stack.newFrame(initPool = listOf(enumNameAsVariable)) { Wrapper.getEnumEntry(enumClass)!!.invokeMethod(valueOfFun) } - } - else -> interpretEnumEntry(it) - } - executionResult.check { result -> return result } - mapOfEnums[it.symbol] = stack.popReturnValue() as Complex - } - - stack.pushReturnValue(mapOfEnums[expression.symbol]!!) - return Next - } - - private fun interpretEnumEntry(enumEntry: IrEnumEntry): ExecutionResult { - val enumClass = enumEntry.symbol.owner.parentAsClass - val enumEntries = enumClass.declarations.filterIsInstance() - - val enumSuperCall = (enumClass.primaryConstructor?.body?.statements?.firstOrNull() as? IrEnumConstructorCall) - if (enumEntries.isNotEmpty() && enumSuperCall != null) { - val valueArguments = listOf( - enumEntry.name.asString().toIrConst(irBuiltIns.stringType), enumEntries.indexOf(enumEntry).toIrConst(irBuiltIns.intType) - ) - valueArguments.forEachIndexed { index, irConst -> enumSuperCall.putValueArgument(index, irConst) } - } - - val enumConstructorCall = enumEntry.initializerExpression?.expression as? IrEnumConstructorCall - ?: throw InterpreterError("Initializer at enum entry ${enumEntry.fqNameWhenAvailable} is null") - val enumClassState = Variable(enumConstructorCall.getThisReceiver(), Common(enumEntry.correspondingClass ?: enumClass)) - val executionResult = stack.newFrame(asSubFrame = true, initPool = listOf(enumClassState)) { - enumConstructorCall.interpret() - } - enumSuperCall?.apply { (0 until this.valueArgumentsCount).forEach { putValueArgument(it, null) } } // restore to null - return executionResult - } - - private fun interpretTypeOperatorCall(expression: IrTypeOperatorCall): ExecutionResult { - val executionResult = expression.argument.interpret().check { return it } + private fun interpretTypeOperatorCall(expression: IrTypeOperatorCall) { val typeClassifier = expression.typeOperand.classifierOrFail val isReified = (typeClassifier.owner as? IrTypeParameter)?.isReified == true val isErased = typeClassifier.owner is IrTypeParameter && !isReified - val typeOperand = if (isReified) (stack.getVariable(typeClassifier).state as KTypeState).irType else expression.typeOperand + val typeOperand = if (isReified) (callStack.getVariable(typeClassifier).state as KTypeState).irType else expression.typeOperand when (expression.operator) { IrTypeOperator.IMPLICIT_COERCION_TO_UNIT -> { - stack.popReturnValue() - getOrCreateObjectValue(irBuiltIns.unitClass.owner).check { return it } + callStack.popState() + // callStack.pushState(getUnitState()) TODO find real use cases for this } IrTypeOperator.CAST, IrTypeOperator.IMPLICIT_CAST -> { - if (!isErased && !stack.peekReturnValue().isSubtypeOf(typeOperand)) { - val convertibleClassName = stack.popReturnValue().irClass.fqNameWhenAvailable - ClassCastException("$convertibleClassName cannot be cast to ${typeOperand.render()}").throwAsUserException() + if (!isErased && !callStack.peekState()!!.isSubtypeOf(typeOperand)) { + val convertibleClassName = callStack.popState().irClass.fqNameWhenAvailable + ClassCastException("$convertibleClassName cannot be cast to ${typeOperand.render()}").handleUserException(environment) } } IrTypeOperator.SAFE_CAST -> { - if (!isErased && !stack.peekReturnValue().isSubtypeOf(typeOperand)) { - stack.popReturnValue() - stack.pushReturnValue(null.toState(irBuiltIns.nothingNType)) + if (!isErased && !callStack.peekState()!!.isSubtypeOf(typeOperand)) { + callStack.popState() + callStack.pushState(null.toState(irBuiltIns.nothingNType)) } } IrTypeOperator.INSTANCEOF -> { - val isInstance = stack.popReturnValue().isSubtypeOf(typeOperand) || isErased - stack.pushReturnValue(isInstance.toState(irBuiltIns.booleanType)) + val isInstance = callStack.popState().isSubtypeOf(typeOperand) || isErased + callStack.pushState(isInstance.toState(irBuiltIns.booleanType)) } IrTypeOperator.NOT_INSTANCEOF -> { - val isInstance = stack.popReturnValue().isSubtypeOf(typeOperand) || isErased - stack.pushReturnValue((!isInstance).toState(irBuiltIns.booleanType)) + val isInstance = callStack.popState().isSubtypeOf(typeOperand) || isErased + callStack.pushState((!isInstance).toState(irBuiltIns.booleanType)) } IrTypeOperator.IMPLICIT_NOTNULL -> { } else -> TODO("${expression.operator} not implemented") } - return executionResult } - private fun interpretVararg(expression: IrVararg): ExecutionResult { + private fun interpretVararg(expression: IrVararg) { fun arrayToList(value: Any?): List { return when (value) { is ByteArray -> value.toList() @@ -755,12 +616,12 @@ class IrInterpreter(val irBuiltIns: IrBuiltIns, private val bodyMap: Map value.toList() is Array<*> -> value.toList() else -> listOf(value) - } + }.reversed() } + // TODO use wrap??? val args = expression.elements.flatMap { - it.interpret().check { executionResult -> return executionResult } - return@flatMap when (val result = stack.popReturnValue()) { + return@flatMap when (val result = callStack.popState()) { is Wrapper -> listOf(result.value) is Primitive<*> -> when { expression.varargElementType.isArray() || expression.varargElementType.isPrimitiveArray() -> listOf(result) @@ -772,7 +633,7 @@ class IrInterpreter(val irBuiltIns: IrBuiltIns, private val bodyMap: Map listOf(result) } - } + }.reversed() val array = when { expression.type.isUnsignedArray() -> { @@ -789,130 +650,84 @@ class IrInterpreter(val irBuiltIns: IrBuiltIns, private val bodyMap: Map args.toPrimitiveStateArray(expression.type) } - stack.pushReturnValue(array) - return Next + callStack.pushState(array) } - private fun interpretSpreadElement(spreadElement: IrSpreadElement): ExecutionResult { - return spreadElement.expression.interpret().check { return it } - } - - private fun interpretTry(expression: IrTry): ExecutionResult { - try { - expression.tryResult.interpret().check(ReturnLabel.EXCEPTION) { return it } // if not exception -> return - val exception = stack.peekReturnValue() - for (catchBlock in expression.catches) { - if (exception.isSubtypeOf(catchBlock.catchParameter.type)) { - catchBlock.interpret().check { return it } - return Next - } + private fun interpretTry(element: IrTry) { + val possibleException = callStack.peekState() + callStack.dropSubFrame() + if (possibleException is ExceptionState) { + // after evaluation of finally, check that there are not unhandled exceptions left + val checkUnhandledException = fun() { + callStack.pushState(possibleException) + callStack.dropFramesUntilTryCatch() } - return Exception - } finally { - expression.finallyExpression?.interpret() - ?.let { if (it.returnLabel == ReturnLabel.REGULAR) stack.popReturnValue() else return it } + callStack.addInstruction(CustomInstruction(checkUnhandledException)) } + callStack.addInstruction(CompoundInstruction(element.finallyExpression)) } - private fun interpretCatch(expression: IrCatch): ExecutionResult { - val catchParameter = Variable(expression.catchParameter.symbol, stack.popReturnValue()) - return stack.newFrame(asSubFrame = true, initPool = listOf(catchParameter)) { - expression.result.interpret() - } - } - - private fun interpretThrow(expression: IrThrow): ExecutionResult { - expression.value.interpret().check { return it } - stack.fixCallEntryPoint(expression) - when (val exception = stack.popReturnValue()) { - is Common -> stack.pushReturnValue(ExceptionState(exception, stack.getStackTrace())) - is Wrapper -> stack.pushReturnValue(ExceptionState(exception, stack.getStackTrace())) - is ExceptionState -> stack.pushReturnValue(exception) + private fun interpretThrow(expression: IrThrow) { + val exception = callStack.popState() + callStack.newSubFrame(expression, listOf()) // temporary frame to get correct stack trace + when (exception) { + is Common -> callStack.pushState(ExceptionState(exception, callStack.getStackTrace())) + is Wrapper -> callStack.pushState(ExceptionState(exception, callStack.getStackTrace())) + is ExceptionState -> callStack.pushState(exception) else -> throw InterpreterError("${exception::class} cannot be used as exception state") } - return Exception + callStack.dropFramesUntilTryCatch() } - private fun interpretStringConcatenation(expression: IrStringConcatenation): ExecutionResult { - val result = StringBuilder() - expression.arguments.forEach { - it.interpret().check { executionResult -> return executionResult } - interpretToString(stack.popReturnValue()).check { executionResult -> return executionResult } - result.append(stack.popReturnValue().asString()) - } - - stack.pushReturnValue(result.toString().toState(expression.type)) - return Next - } - - private fun interpretToString(state: State): ExecutionResult { - val result = when (state) { - is Primitive<*> -> state.value.toString() - is Wrapper -> state.value.toString() - is Common -> { - val toStringFun = state.getToStringFunction() - return stack.newFrame(initPool = mutableListOf(Variable(toStringFun.getReceiver()!!, state))) { - toStringFun.body?.let { toStringFun.interpret() } ?: calculateBuiltIns(toStringFun) - } + private fun interpretStringConcatenation(expression: IrStringConcatenation) { + val result = mutableListOf() + repeat(expression.arguments.size) { + result += when (val state = callStack.popState()) { + is Primitive<*> -> state.value.toString() + is Wrapper -> state.value.toString() + else -> state.toString() } - else -> state.toString() } - stack.pushReturnValue(result.toState(irBuiltIns.stringType)) - return Next + + callStack.dropSubFrame() + callStack.pushState(result.reversed().joinToString(separator = "").toState(expression.type)) } - private fun interpretFunctionExpression(expression: IrFunctionExpression): ExecutionResult { + private fun interpretFunctionExpression(expression: IrFunctionExpression) { val function = KFunctionState(expression.function, expression.type.classOrNull!!.owner) - if (expression.function.isLocal) function.fields.addAll(stack.getAll()) // TODO save only necessary declarations - stack.pushReturnValue(function) - return Next + if (expression.function.isLocal) callStack.storeUpValues(function) + callStack.pushState(function) } - private fun interpretFunctionReference(reference: IrFunctionReference): ExecutionResult { + private fun interpretFunctionReference(reference: IrFunctionReference) { val function = KFunctionState(reference) + val irFunction = function.irFunction - // dispatch receiver processing - val rawDispatchReceiver = reference.dispatchReceiver - rawDispatchReceiver?.interpret()?.check { return it } - val dispatchReceiver = rawDispatchReceiver?.let { stack.popReturnValue() }?.checkNullability(rawDispatchReceiver.type) + val extensionReceiver = reference.extensionReceiver?.let { callStack.popState() } + val dispatchReceiver = reference.dispatchReceiver?.let { callStack.popState() } - // extension receiver processing - val rawExtensionReceiver = reference.extensionReceiver - rawExtensionReceiver?.interpret()?.check { return it } - val extensionReceiver = rawExtensionReceiver?.let { stack.popReturnValue() }?.checkNullability(rawExtensionReceiver.type) + callStack.dropSubFrame() + irFunction.getDispatchReceiver()?.let { dispatchReceiver?.let { receiver -> function.fields.add(Variable(it, receiver)) } } + irFunction.getExtensionReceiver()?.let { extensionReceiver?.let { receiver -> function.fields.add(Variable(it, receiver)) } } - function.irFunction.getDispatchReceiver()?.let { dispatchReceiver?.let { receiver -> function.fields.add(Variable(it, receiver)) } } - function.irFunction.getExtensionReceiver()?.let { extensionReceiver?.let { receiver -> function.fields.add(Variable(it, receiver)) } } - - if (function.irFunction.isLocal) function.fields.addAll(stack.getAll()) // TODO save only necessary declarations - stack.pushReturnValue(function) - return Next + if (irFunction.isLocal) callStack.storeUpValues(function) + callStack.pushState(function) } - private fun interpretComposite(expression: IrComposite): ExecutionResult { - return when (expression.origin) { - IrStatementOrigin.DESTRUCTURING_DECLARATION -> interpretStatements(expression.statements) - null -> interpretStatements(expression.statements) // is null for body of do while loop - else -> TODO("${expression.origin} not implemented") - } - } - - private fun interpretPropertyReference(propertyReference: IrPropertyReference): ExecutionResult { - val dispatchReceiver = propertyReference.dispatchReceiver?.interpret()?.check { return it }?.let { stack.popReturnValue() } + private fun interpretPropertyReference(propertyReference: IrPropertyReference) { + val dispatchReceiver = propertyReference.dispatchReceiver?.let { callStack.popState() } // it is impossible to get KProperty2 through ::, so extension receiver is always null val propertyState = KPropertyState(propertyReference, dispatchReceiver) - stack.pushReturnValue(propertyState) - return Next + callStack.pushState(propertyState) } - private fun interpretClassReference(classReference: IrClassReference): ExecutionResult { + private fun interpretClassReference(classReference: IrClassReference) { when (classReference.symbol) { is IrTypeParameterSymbol -> { // reified - val kTypeState = stack.getVariable(classReference.symbol).state as KTypeState - stack.pushReturnValue(KClassState(kTypeState.irType.classOrNull!!.owner, classReference.type.classOrNull!!.owner)) + val kTypeState = callStack.getVariable(classReference.symbol).state as KTypeState + callStack.pushState(KClassState(kTypeState.irType.classOrNull!!.owner, classReference.type.classOrNull!!.owner)) } - else -> stack.pushReturnValue(KClassState(classReference)) + else -> callStack.pushState(KClassState(classReference)) } - return Next } -} \ No newline at end of file +} diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrInterpreterEnvironment.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrInterpreterEnvironment.kt new file mode 100644 index 00000000000..cd5579c40fd --- /dev/null +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/IrInterpreterEnvironment.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2010-2021 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.ir.interpreter + +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns +import org.jetbrains.kotlin.ir.interpreter.stack.CallStack +import org.jetbrains.kotlin.ir.interpreter.state.Complex +import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.util.isSubclassOf + +internal class IrInterpreterEnvironment(val irBuiltIns: IrBuiltIns, val callStack: CallStack) { + val irExceptions = mutableListOf() + var mapOfEnums = mutableMapOf() + var mapOfObjects = mutableMapOf() + + private constructor(environment: IrInterpreterEnvironment) : this(environment.irBuiltIns, CallStack()) { + irExceptions.addAll(environment.irExceptions) + mapOfEnums = environment.mapOfEnums + mapOfObjects = environment.mapOfObjects + } + + constructor(irModule: IrModuleFragment) : this(irModule.irBuiltins, CallStack()) { + irExceptions.addAll( + irModule.files + .flatMap { it.declarations } + .filterIsInstance() + .filter { it.isSubclassOf(irBuiltIns.throwableClass.owner) } + ) + } + + fun copyWithNewCallStack(): IrInterpreterEnvironment { + return IrInterpreterEnvironment(this) + } + + companion object { + const val MAX_STACK = 10_000 + const val MAX_COMMANDS = 1_000_000 + } +} diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/Label.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/Label.kt deleted file mode 100644 index c7c6e2fd81c..00000000000 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/Label.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2010-2020 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.ir.interpreter - -import org.jetbrains.kotlin.ir.interpreter.stack.Stack -import org.jetbrains.kotlin.ir.interpreter.state.Primitive -import org.jetbrains.kotlin.ir.interpreter.state.isSubtypeOf -import org.jetbrains.kotlin.ir.IrElement -import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction -import org.jetbrains.kotlin.ir.expressions.* -import org.jetbrains.kotlin.ir.interpreter.exceptions.throwAsUserException -import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol -import org.jetbrains.kotlin.ir.types.IrType -import org.jetbrains.kotlin.ir.types.classifierOrFail -import org.jetbrains.kotlin.ir.types.classifierOrNull -import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable -import org.jetbrains.kotlin.ir.util.render - -enum class ReturnLabel { - REGULAR, RETURN, BREAK_LOOP, BREAK_WHEN, CONTINUE, EXCEPTION -} - -open class ExecutionResult(val returnLabel: ReturnLabel, private val owner: IrElement? = null) { - fun getNextLabel(irElement: IrElement, interpret: IrElement.() -> ExecutionResult): ExecutionResult { - return when (returnLabel) { - ReturnLabel.RETURN -> when (irElement) { - is IrCall, is IrReturnableBlock, is IrSimpleFunction -> if (owner == irElement) Next else this - else -> this - } - ReturnLabel.BREAK_WHEN -> when (irElement) { - is IrWhen -> Next - else -> this - } - ReturnLabel.BREAK_LOOP -> when (irElement) { - is IrWhileLoop, is IrDoWhileLoop -> if (owner == irElement) Next else this - else -> this - } - ReturnLabel.CONTINUE -> when (irElement) { - is IrWhileLoop -> if (owner == irElement) irElement.interpret() else this - is IrDoWhileLoop -> { - if (owner != irElement) return this - error("Continue must be handled inside DoWhile interpreter function") - } - else -> this - } - ReturnLabel.EXCEPTION -> Exception - ReturnLabel.REGULAR -> Next - } - } - - fun addOwnerInfo(owner: IrElement): ExecutionResult { - return ExecutionResult(returnLabel, owner) - } -} - -inline fun ExecutionResult.check( - vararg toCheckLabel: ReturnLabel = arrayOf(ReturnLabel.REGULAR), returnBlock: (ExecutionResult) -> Unit -): ExecutionResult { - if (this.returnLabel !in toCheckLabel) returnBlock(this) - return this -} - -/** - * This method is analog of `checkcast` jvm bytecode operation. Throw exception whenever actual type is not a subtype of expected. - */ -internal fun ExecutionResult.implicitCastIfNeeded(expectedType: IrType, actualType: IrType, stack: Stack): ExecutionResult { - if (actualType.classifierOrNull !is IrTypeParameterSymbol) return this - - if (expectedType.classifierOrFail is IrTypeParameterSymbol) return this - - val actualState = stack.peekReturnValue() - if (actualState is Primitive<*> && actualState.value == null) return this // this is handled as NullPointerException - - if (!actualState.isSubtypeOf(expectedType)) { - val convertibleClassName = stack.popReturnValue().irClass.fqNameWhenAvailable - ClassCastException("$convertibleClassName cannot be cast to ${expectedType.render()}").throwAsUserException() - } - return this -} - -object Next : ExecutionResult(ReturnLabel.REGULAR) -object Return : ExecutionResult(ReturnLabel.RETURN) -object BreakLoop : ExecutionResult(ReturnLabel.BREAK_LOOP) -object BreakWhen : ExecutionResult(ReturnLabel.BREAK_WHEN) -object Continue : ExecutionResult(ReturnLabel.CONTINUE) -object Exception : ExecutionResult(ReturnLabel.EXCEPTION) \ No newline at end of file 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 52b350fab98..99a06bc1104 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 @@ -13,12 +13,13 @@ import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrErrorExpressionImpl import org.jetbrains.kotlin.ir.interpreter.builtins.evaluateIntrinsicAnnotation -import org.jetbrains.kotlin.ir.interpreter.exceptions.throwAsUserException import org.jetbrains.kotlin.ir.interpreter.stack.Variable import org.jetbrains.kotlin.ir.interpreter.state.* import org.jetbrains.kotlin.ir.interpreter.proxy.Proxy import org.jetbrains.kotlin.ir.interpreter.proxy.wrap +import org.jetbrains.kotlin.ir.interpreter.state.reflection.KTypeState import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.impl.buildSimpleType @@ -28,7 +29,6 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.types.Variance import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly -import org.jetbrains.kotlin.utils.addToStdlib.safeAs import java.lang.invoke.MethodType import kotlin.math.min @@ -53,6 +53,9 @@ internal fun State.toIrExpression(expression: IrExpression): IrExpression { type.isPrimitiveType() || type.isString() -> this.value.toIrConst(type, start, end) else -> expression // TODO support for arrays } + is ExceptionState -> { + IrErrorExpressionImpl(expression.startOffset, expression.endOffset, expression.type, "\n" + this.getFullDescription()) + } is Complex -> { val stateType = this.irClass.defaultType when { @@ -234,17 +237,51 @@ fun IrClass.internalName(): String { return internalName.toString() } -inline fun withExceptionHandler(block: () -> Any?): Any? { +internal inline fun withExceptionHandler(environment: IrInterpreterEnvironment, block: () -> Unit) { try { - return block() + block() + // check if during proxy interpretation was an exception + val possibleException = environment.callStack.peekState() as? ExceptionState + if (possibleException != null) environment.callStack.dropFramesUntilTryCatch() } catch (e: Throwable) { - e.throwAsUserException() + e.handleUserException(environment) } } -internal fun IrFunction.getArgsForMethodInvocation(interpreter: IrInterpreter, methodType: MethodType, args: List): List { +internal fun Throwable.handleUserException(environment: IrInterpreterEnvironment) { + val exceptionName = this::class.java.simpleName + val irExceptionClass = environment.irExceptions.firstOrNull { it.name.asString() == exceptionName } + ?: environment.irBuiltIns.throwableClass.owner + environment.callStack.pushState(ExceptionState(this, irExceptionClass, environment.callStack.getStackTrace())) + environment.callStack.dropFramesUntilTryCatch() +} + +/** + * This method is analog of `checkcast` jvm bytecode operation. Throw exception whenever actual type is not a subtype of expected. + */ +internal fun IrFunction?.checkCast(environment: IrInterpreterEnvironment): Boolean { + this ?: return true + val actualType = this.returnType + if (actualType.classifierOrNull !is IrTypeParameterSymbol) return true + + // TODO expectedType can be missing for functions called as proxy + val expectedType = (environment.callStack.getVariable(this.symbol).state as? KTypeState)?.irType ?: return true + if (expectedType.classifierOrFail is IrTypeParameterSymbol) return true + + val actualState = environment.callStack.peekState() ?: return true + if (actualState is Primitive<*> && actualState.value == null) return true // this is handled in checkNullability + + if (!actualState.isSubtypeOf(expectedType)) { + val convertibleClassName = environment.callStack.popState().irClass.fqNameWhenAvailable + ClassCastException("$convertibleClassName cannot be cast to ${expectedType.render()}").handleUserException(environment) + return false + } + return true +} + +internal fun IrFunction.getArgsForMethodInvocation(interpreter: IrInterpreter, methodType: MethodType, args: List): List { val argsValues = args - .mapIndexed { index, variable -> variable.state.wrap(interpreter, methodType.parameterType(index)) } + .mapIndexed { index, state -> state.wrap(interpreter, methodType.parameterType(index)) } .toMutableList() // TODO if vararg isn't last parameter diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/exceptions/UserException.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/exceptions/UserException.kt deleted file mode 100644 index b359c240c5b..00000000000 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/exceptions/UserException.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2010-2020 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.ir.interpreter.exceptions - -/** - * This class used to wrap 2 types of exceptions: - * 1. exceptions from JVM such as: ArithmeticException, StackOverflowError and others - * 2. exceptions defined by user - */ -class UserException(val exception: Throwable) : InterpreterException() { - override fun fillInStackTrace(): Throwable = this -} - -fun Throwable.throwAsUserException(): Nothing { - throw UserException(this) -} \ No newline at end of file diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/intrinsics/IntrinsicEvaluator.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/intrinsics/IntrinsicEvaluator.kt index 002a674970d..639a467f0bf 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/intrinsics/IntrinsicEvaluator.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/intrinsics/IntrinsicEvaluator.kt @@ -5,25 +5,24 @@ package org.jetbrains.kotlin.ir.interpreter.intrinsics -import org.jetbrains.kotlin.ir.interpreter.ExecutionResult -import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterMethodNotFoundError -import org.jetbrains.kotlin.ir.interpreter.stack.Stack -import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.interpreter.Instruction +import org.jetbrains.kotlin.ir.interpreter.IrInterpreterEnvironment +import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterMethodNotFoundError -internal class IntrinsicEvaluator { - fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult { +internal object IntrinsicEvaluator { + fun unwindInstructions(irFunction: IrFunction, environment: IrInterpreterEnvironment): List { return when { - EmptyArray.equalTo(irFunction) -> EmptyArray.evaluate(irFunction, stack, interpret) - ArrayOf.equalTo(irFunction) -> ArrayOf.evaluate(irFunction, stack, interpret) - ArrayOfNulls.equalTo(irFunction) -> ArrayOfNulls.evaluate(irFunction, stack, interpret) - EnumValues.equalTo(irFunction) -> EnumValues.evaluate(irFunction, stack, interpret) - EnumValueOf.equalTo(irFunction) -> EnumValueOf.evaluate(irFunction, stack, interpret) - EnumHashCode.equalTo(irFunction) -> EnumHashCode.evaluate(irFunction, stack, interpret) - JsPrimitives.equalTo(irFunction) -> JsPrimitives.evaluate(irFunction, stack, interpret) - ArrayConstructor.equalTo(irFunction) -> ArrayConstructor.evaluate(irFunction, stack, interpret) - SourceLocation.equalTo(irFunction) -> SourceLocation.evaluate(irFunction, stack, interpret) - AssertIntrinsic.equalTo(irFunction) -> AssertIntrinsic.evaluate(irFunction, stack, interpret) + EmptyArray.equalTo(irFunction) -> EmptyArray.unwind(irFunction, environment) + ArrayOf.equalTo(irFunction) -> ArrayOf.unwind(irFunction, environment) + ArrayOfNulls.equalTo(irFunction) -> ArrayOfNulls.unwind(irFunction, environment) + EnumValues.equalTo(irFunction) -> EnumValues.unwind(irFunction, environment) + EnumValueOf.equalTo(irFunction) -> EnumValueOf.unwind(irFunction, environment) + EnumHashCode.equalTo(irFunction) -> EnumHashCode.unwind(irFunction, environment) + JsPrimitives.equalTo(irFunction) -> JsPrimitives.unwind(irFunction, environment) + ArrayConstructor.equalTo(irFunction) -> ArrayConstructor.unwind(irFunction, environment) + SourceLocation.equalTo(irFunction) -> SourceLocation.unwind(irFunction, environment) + AssertIntrinsic.equalTo(irFunction) -> AssertIntrinsic.unwind(irFunction, environment) else -> throw InterpreterMethodNotFoundError("Method ${irFunction.name} hasn't implemented") } } diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/intrinsics/IntrinsicImplementations.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/intrinsics/IntrinsicImplementations.kt index e9f1c14d0cf..a30c1c162fc 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/intrinsics/IntrinsicImplementations.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/intrinsics/IntrinsicImplementations.kt @@ -5,15 +5,14 @@ package org.jetbrains.kotlin.ir.interpreter.intrinsics -import org.jetbrains.kotlin.ir.interpreter.* -import org.jetbrains.kotlin.ir.interpreter.stack.Stack -import org.jetbrains.kotlin.ir.interpreter.stack.Variable -import org.jetbrains.kotlin.ir.interpreter.state.* -import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrEnumEntry import org.jetbrains.kotlin.ir.declarations.IrFunction -import org.jetbrains.kotlin.ir.interpreter.exceptions.throwAsUserException +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl +import org.jetbrains.kotlin.ir.interpreter.* +import org.jetbrains.kotlin.ir.interpreter.state.* import org.jetbrains.kotlin.ir.interpreter.state.reflection.KFunctionState import org.jetbrains.kotlin.ir.interpreter.state.reflection.KTypeState import org.jetbrains.kotlin.ir.types.IrSimpleType @@ -22,12 +21,20 @@ import org.jetbrains.kotlin.ir.types.impl.buildSimpleType import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection import org.jetbrains.kotlin.ir.types.isArray import org.jetbrains.kotlin.ir.types.isCharArray -import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable import org.jetbrains.kotlin.types.Variance internal sealed class IntrinsicBase { abstract fun equalTo(irFunction: IrFunction): Boolean - abstract fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult + abstract fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) + abstract fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List + + fun customEvaluateInstruction(irFunction: IrFunction, environment: IrInterpreterEnvironment): CustomInstruction { + return CustomInstruction { + evaluate(irFunction, environment) + environment.callStack.dropFrameAndCopyResult() + } + } } internal object EmptyArray : IntrinsicBase() { @@ -36,13 +43,13 @@ internal object EmptyArray : IntrinsicBase() { return fqName in setOf("kotlin.emptyArray", "kotlin.ArrayIntrinsicsKt.emptyArray") } - override fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult { - val typeArgument = irFunction.typeParameters.map { stack.getVariable(it.symbol) }.single().state as KTypeState - val returnType = (irFunction.returnType as IrSimpleType).buildSimpleType { - arguments = listOf(makeTypeProjection(typeArgument.irType, Variance.INVARIANT)) - } - stack.pushReturnValue(emptyArray().toState(returnType)) - return Next + override fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List { + return listOf(customEvaluateInstruction(irFunction, environment)) + } + + override fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) { + val returnType = environment.callStack.getVariable(irFunction.symbol).state as KTypeState + environment.callStack.pushState(emptyArray().toState(returnType.irType)) } } @@ -52,10 +59,14 @@ internal object ArrayOf : IntrinsicBase() { return fqName == "kotlin.arrayOf" } - override fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult { - val elementsVariable = irFunction.valueParameters.single().symbol - stack.pushReturnValue(stack.getVariable(elementsVariable).state) - return Next + override fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List { + return listOf(customEvaluateInstruction(irFunction, environment)) + } + + override fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) { + val elementsSymbol = irFunction.valueParameters.single().symbol + val varargVariable = environment.callStack.getVariable(elementsSymbol).state + environment.callStack.pushState(varargVariable) } } @@ -65,15 +76,19 @@ internal object ArrayOfNulls : IntrinsicBase() { return fqName == "kotlin.arrayOfNulls" } - override fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult { - val size = stack.getVariable(irFunction.valueParameters.first().symbol).state.asInt() + override fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List { + return listOf(customEvaluateInstruction(irFunction, environment)) + } + + override fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) { + val size = environment.callStack.getVariable(irFunction.valueParameters.first().symbol).state.asInt() val array = arrayOfNulls(size) - val typeArgument = irFunction.typeParameters.map { stack.getVariable(it.symbol) }.single().state as KTypeState + val typeArgument = irFunction.typeParameters.map { environment.callStack.getVariable(it.symbol) }.single().state as KTypeState val returnType = (irFunction.returnType as IrSimpleType).buildSimpleType { arguments = listOf(makeTypeProjection(typeArgument.irType, Variance.INVARIANT)) } - stack.pushReturnValue(array.toState(returnType)) - return Next + + environment.callStack.pushState(array.toState(returnType)) } } @@ -83,19 +98,28 @@ internal object EnumValues : IntrinsicBase() { return (fqName == "kotlin.enumValues" || fqName.endsWith(".values")) && irFunction.valueParameters.isEmpty() } - override fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult { - val enumClass = when (irFunction.fqNameWhenAvailable.toString()) { + private fun getEnumClass(irFunction: IrFunction, environment: IrInterpreterEnvironment): IrClass { + return when (irFunction.fqNameWhenAvailable.toString()) { "kotlin.enumValues" -> { - val kType = stack.getVariable(irFunction.typeParameters.first().symbol).state as KTypeState + val kType = environment.callStack.getVariable(irFunction.typeParameters.first().symbol).state as KTypeState kType.irType.classOrNull!!.owner } else -> irFunction.parent as IrClass } + } + override fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List { + val enumClass = getEnumClass(irFunction, environment) val enumEntries = enumClass.declarations.filterIsInstance() - .map { entry -> entry.interpret().check { return it }.let { stack.popReturnValue() as Common } } - stack.pushReturnValue(enumEntries.toTypedArray().toState(irFunction.returnType)) - return Next + + return listOf(customEvaluateInstruction(irFunction, environment)) + enumEntries.reversed().map { CompoundInstruction(it) } + } + + override fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) { + val enumClass = getEnumClass(irFunction, environment) + + val enumEntries = enumClass.declarations.filterIsInstance().map { environment.mapOfEnums[it.symbol] } + environment.callStack.pushState(enumEntries.toTypedArray().toState(irFunction.returnType)) } } @@ -105,20 +129,34 @@ internal object EnumValueOf : IntrinsicBase() { return (fqName == "kotlin.enumValueOf" || fqName.endsWith(".valueOf")) && irFunction.valueParameters.size == 1 } - override fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult { - val enumClass = when (irFunction.fqNameWhenAvailable.toString()) { + private fun getEnumClass(irFunction: IrFunction, environment: IrInterpreterEnvironment): IrClass { + return when (irFunction.fqNameWhenAvailable.toString()) { "kotlin.enumValueOf" -> { - val kType = stack.getVariable(irFunction.typeParameters.first().symbol).state as KTypeState + val kType = environment.callStack.getVariable(irFunction.typeParameters.first().symbol).state as KTypeState kType.irType.classOrNull!!.owner } else -> irFunction.parent as IrClass } - val enumEntryName = stack.getVariable(irFunction.valueParameters.first().symbol).state.asString() - val enumEntry = enumClass.declarations.filterIsInstance().singleOrNull { it.name.asString() == enumEntryName } - enumEntry?.interpret()?.check { return it } - ?: IllegalArgumentException("No enum constant ${enumClass.fqNameWhenAvailable}.$enumEntryName").throwAsUserException() + } - return Next + private fun getEnumEntryByName(irFunction: IrFunction, environment: IrInterpreterEnvironment): IrEnumEntry? { + val enumClass = getEnumClass(irFunction, environment) + val enumEntryName = environment.callStack.getVariable(irFunction.valueParameters.first().symbol).state.asString() + val enumEntry = enumClass.declarations.filterIsInstance().singleOrNull { it.name.asString() == enumEntryName } + if (enumEntry == null) { + IllegalArgumentException("No enum constant ${enumClass.fqNameWhenAvailable}.$enumEntryName").handleUserException(environment) + } + return enumEntry + } + + override fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List { + val enumEntry = getEnumEntryByName(irFunction, environment) ?: return emptyList() + return listOf(customEvaluateInstruction(irFunction, environment), CompoundInstruction(enumEntry)) + } + + override fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) { + val enumEntry = getEnumEntryByName(irFunction, environment)!! + environment.callStack.pushState(environment.mapOfEnums[enumEntry.symbol]!!) } } @@ -128,10 +166,13 @@ internal object EnumHashCode : IntrinsicBase() { return fqName == "kotlin.Enum.hashCode" } - override fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult { - val hashCode = stack.getAll().single().state.hashCode() - stack.pushReturnValue(hashCode.toState(irFunction.returnType)) - return Next + override fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List { + return listOf(customEvaluateInstruction(irFunction, environment)) + } + + override fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) { + val hashCode = environment.callStack.getVariable(irFunction.dispatchReceiverParameter!!.symbol).state.hashCode() + environment.callStack.pushState(hashCode.toState(irFunction.returnType)) } } @@ -141,19 +182,22 @@ internal object JsPrimitives : IntrinsicBase() { return fqName == "kotlin.Long." || fqName == "kotlin.Char." } - override fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult { + override fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List { + return listOf(customEvaluateInstruction(irFunction, environment)) + } + + override fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) { when (irFunction.fqNameWhenAvailable.toString()) { "kotlin.Long." -> { - val low = stack.getVariable(irFunction.valueParameters[0].symbol).state.asInt() - val high = stack.getVariable(irFunction.valueParameters[1].symbol).state.asInt() - stack.pushReturnValue((high.toLong().shl(32) + low).toState(irFunction.returnType)) + val low = environment.callStack.getVariable(irFunction.valueParameters[0].symbol).state.asInt() + val high = environment.callStack.getVariable(irFunction.valueParameters[1].symbol).state.asInt() + environment.callStack.pushState((high.toLong().shl(32) + low).toState(irFunction.returnType)) } "kotlin.Char." -> { - val value = stack.getVariable(irFunction.valueParameters[0].symbol).state.asInt() - stack.pushReturnValue(value.toChar().toState(irFunction.returnType)) + val value = environment.callStack.getVariable(irFunction.valueParameters[0].symbol).state.asInt() + environment.callStack.pushState(value.toChar().toState(irFunction.returnType)) } } - return Next } } @@ -163,24 +207,36 @@ internal object ArrayConstructor : IntrinsicBase() { return fqName.matches("kotlin\\.(Byte|Char|Short|Int|Long|Float|Double|Boolean|)Array\\.".toRegex()) } - override fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult { + override fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List { + if (irFunction.valueParameters.size == 1) return listOf(customEvaluateInstruction(irFunction, environment)) + val instructions = mutableListOf(customEvaluateInstruction(irFunction, environment)) + val sizeDescriptor = irFunction.valueParameters[0].symbol - val size = stack.getVariable(sizeDescriptor).state.asInt() + val size = environment.callStack.getVariable(sizeDescriptor).state.asInt() + + val initDescriptor = irFunction.valueParameters[1].symbol + val initLambda = environment.callStack.getVariable(initDescriptor).state as KFunctionState + environment.callStack.loadUpValues(initLambda) + val function = initLambda.irFunction as IrSimpleFunction + val index = initLambda.irFunction.valueParameters.single() + for (i in 0 until size) { + val call = IrCallImpl.fromSymbolOwner(0, 0, function.returnType, function.symbol) + call.putValueArgument(0, IrConstImpl.int(0, 0, index.type, i)) + instructions += CompoundInstruction(call) + } + + return instructions + } + + override fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) { + val sizeDescriptor = irFunction.valueParameters[0].symbol + val size = environment.callStack.getVariable(sizeDescriptor).state.asInt() val arrayValue = MutableList(size) { if (irFunction.returnType.isCharArray()) 0.toChar() else 0 } if (irFunction.valueParameters.size == 2) { - val initDescriptor = irFunction.valueParameters[1].symbol - val initLambda = stack.getVariable(initDescriptor).state as KFunctionState - val index = initLambda.irFunction.valueParameters.single() - val nonLocalDeclarations = initLambda.extractNonLocalDeclarations() for (i in 0 until size) { - val indexVar = listOf(Variable(index.symbol, i.toState(index.type))) - // TODO throw exception if label != RETURN - stack.newFrame( - asSubFrame = initLambda.irFunction.isLocal || initLambda.irFunction.isInline, - initPool = nonLocalDeclarations + indexVar - ) { initLambda.irFunction.body!!.interpret() }.check(ReturnLabel.RETURN) { return it } - arrayValue[i] = stack.popReturnValue().let { + arrayValue[i] = environment.callStack.popState().let { + // TODO wrap when (it) { is Wrapper -> it.value is Primitive<*> -> if (it.type.isArray() || it.type.isPrimitiveArray()) it else it.value @@ -190,8 +246,8 @@ internal object ArrayConstructor : IntrinsicBase() { } } - stack.pushReturnValue(arrayValue.toPrimitiveStateArray(irFunction.parentAsClass.defaultType)) - return Next + val type = environment.callStack.getVariable(irFunction.symbol).state as KTypeState + environment.callStack.pushState(arrayValue.toPrimitiveStateArray(type.irType)) } } @@ -201,9 +257,12 @@ internal object SourceLocation : IntrinsicBase() { return fqName == "kotlin.experimental.sourceLocation" || fqName == "kotlin.experimental.SourceLocationKt.sourceLocation" } - override fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult { - stack.pushReturnValue(stack.getCurrentStackInfo().toState(irFunction.returnType)) - return Next + override fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List { + return listOf(customEvaluateInstruction(irFunction, environment)) + } + + override fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) { + environment.callStack.pushState(environment.callStack.getFileAndPositionInfo().toState(irFunction.returnType)) } } @@ -213,17 +272,22 @@ internal object AssertIntrinsic : IntrinsicBase() { return fqName == "kotlin.PreconditionsKt.assert" } - override fun evaluate(irFunction: IrFunction, stack: Stack, interpret: IrElement.() -> ExecutionResult): ExecutionResult { - val value = stack.getVariable(irFunction.valueParameters.first().symbol).state.asBoolean() + override fun unwind(irFunction: IrFunction, environment: IrInterpreterEnvironment): List { + if (irFunction.valueParameters.size == 1) return listOf(customEvaluateInstruction(irFunction, environment)) + + val messageLambda = environment.callStack.getVariable(irFunction.valueParameters.last().symbol).state as KFunctionState + val function = messageLambda.irFunction as IrSimpleFunction + val call = IrCallImpl.fromSymbolOwner(0, 0, function.returnType, function.symbol) + + return listOf(customEvaluateInstruction(irFunction, environment), CompoundInstruction(call)) + } + + override fun evaluate(irFunction: IrFunction, environment: IrInterpreterEnvironment) { + val value = environment.callStack.getVariable(irFunction.valueParameters.first().symbol).state.asBoolean() + if (value) return when (irFunction.valueParameters.size) { - 1 -> if (!value) AssertionError("Assertion failed").throwAsUserException() - 2 -> if (!value) { - val messageLambda = stack.getVariable(irFunction.valueParameters.last().symbol).state as KFunctionState - stack.newFrame(asSubFrame = true) { messageLambda.irFunction.body!!.interpret() } - .check(ReturnLabel.RETURN) { return it } - AssertionError(stack.popReturnValue().asString()).throwAsUserException() - } + 1 -> AssertionError("Assertion failed").handleUserException(environment) + 2 -> AssertionError(environment.callStack.popState().asString()).handleUserException(environment) } - return Next } } \ No newline at end of file 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 ae4aeca929f..b2dd36f11a5 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 @@ -33,7 +33,7 @@ internal class CommonProxy private constructor( equalsFun.getDispatchReceiver()!!.let { valueArguments.add(Variable(it, state)) } valueArguments.add(Variable(equalsFun.valueParameters.single().symbol, other.state)) - return with(interpreter) { equalsFun.interpret(valueArguments) } as Boolean + return with(interpreter) { equalsFun.proxyInterpret(valueArguments) } as Boolean } override fun hashCode(): Int { @@ -42,7 +42,7 @@ internal class CommonProxy private constructor( if (hashCodeFun.isFakeOverriddenFromAny() || calledFromBuiltIns) return System.identityHashCode(state) hashCodeFun.getDispatchReceiver()!!.let { valueArguments.add(Variable(it, state)) } - return with(interpreter) { hashCodeFun.interpret(valueArguments) } as Int + return with(interpreter) { hashCodeFun.proxyInterpret(valueArguments) } as Int } override fun toString(): String { @@ -53,7 +53,7 @@ internal class CommonProxy private constructor( } toStringFun.getDispatchReceiver()!!.let { valueArguments.add(Variable(it, state)) } - return with(interpreter) { toStringFun.interpret(valueArguments) } as String + return with(interpreter) { toStringFun.proxyInterpret(valueArguments) } as String } companion object { @@ -79,7 +79,7 @@ internal class CommonProxy private constructor( valueArguments += Variable(irFunction.getDispatchReceiver()!!, commonProxy.state) valueArguments += irFunction.valueParameters .mapIndexed { index, parameter -> Variable(parameter.symbol, args[index].toState(parameter.type)) } - with(interpreter) { irFunction.interpret(valueArguments, method.returnType) } + with(interpreter) { irFunction.proxyInterpret(valueArguments, method.returnType) } } } } diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/reflection/KFunctionProxy.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/reflection/KFunctionProxy.kt index 9efcf088edd..64321d3f763 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/reflection/KFunctionProxy.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/reflection/KFunctionProxy.kt @@ -48,7 +48,7 @@ internal class KFunctionProxy( val extensionReceiver = state.irFunction.extensionReceiverParameter?.let { Variable(it.symbol, args[index++].toState(it.type)) } val valueArguments = listOfNotNull(dispatchReceiver, extensionReceiver) + state.irFunction.valueParameters.map { parameter -> Variable(parameter.symbol, args[index++].toState(parameter.type)) } - return with(interpreter) { state.irFunction.interpret(valueArguments) } + return with(interpreter) { state.irFunction.proxyInterpret(valueArguments) } } override fun callBy(args: Map): Any? { diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/reflection/KProperty1Proxy.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/reflection/KProperty1Proxy.kt index 535957a7eed..a1e0d68aca9 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/reflection/KProperty1Proxy.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/reflection/KProperty1Proxy.kt @@ -6,8 +6,6 @@ package org.jetbrains.kotlin.ir.interpreter.proxy.reflection import org.jetbrains.kotlin.ir.interpreter.IrInterpreter -import org.jetbrains.kotlin.ir.interpreter.proxy.Proxy -import org.jetbrains.kotlin.ir.interpreter.proxy.wrap import org.jetbrains.kotlin.ir.interpreter.stack.Variable import org.jetbrains.kotlin.ir.interpreter.state.reflection.KPropertyState import org.jetbrains.kotlin.ir.interpreter.toState @@ -24,7 +22,7 @@ internal open class KProperty1Proxy( checkArguments(1, args.size) val receiverParameter = getter.dispatchReceiverParameter ?: getter.extensionReceiverParameter val receiver = Variable(receiverParameter!!.symbol, args[0].toState(receiverParameter.type)) - return with(this@KProperty1Proxy.interpreter) { getter.interpret(listOf(receiver)) } + return with(this@KProperty1Proxy.interpreter) { getter.proxyInterpret(listOf(receiver)) } } override fun callBy(args: Map): Any? { @@ -54,7 +52,7 @@ internal class KMutableProperty1Proxy( val receiver = Variable(receiverParameter!!.symbol, args[0].toState(receiverParameter.type)) val valueParameter = setter.valueParameters.single() val value = Variable(valueParameter.symbol, args[1].toState(valueParameter.type)) - with(this@KMutableProperty1Proxy.interpreter) { setter.interpret(listOf(receiver, value)) } + with(this@KMutableProperty1Proxy.interpreter) { setter.proxyInterpret(listOf(receiver, value)) } } override fun callBy(args: Map) { diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/reflection/KProperty2Proxy.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/reflection/KProperty2Proxy.kt index 1f3be9f429c..cd3641f1c4f 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/reflection/KProperty2Proxy.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/proxy/reflection/KProperty2Proxy.kt @@ -14,15 +14,15 @@ import kotlin.reflect.* internal open class KProperty2Proxy( override val state: KPropertyState, override val interpreter: IrInterpreter ) : AbstractKPropertyProxy(state, interpreter), KProperty2 { - override val getter: KProperty2.Getter = - object : Getter(state.property.getter!!), KProperty2.Getter { + override val getter: KProperty2.Getter + get() = object : Getter(state.property.getter!!), KProperty2.Getter { override fun invoke(p1: Any?, p2: Any?): Any? = call(p1, p2) override fun call(vararg args: Any?): Any? { checkArguments(2, args.size) val dispatch = Variable(getter.dispatchReceiverParameter!!.symbol, args[0].toState(getter.dispatchReceiverParameter!!.type)) val extension = Variable(getter.extensionReceiverParameter!!.symbol, args[1].toState(getter.extensionReceiverParameter!!.type)) - return with(this@KProperty2Proxy.interpreter) { getter.interpret(listOf(dispatch, extension)) } + return with(this@KProperty2Proxy.interpreter) { getter.proxyInterpret(listOf(dispatch, extension)) } } override fun callBy(args: Map): Any? { diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/stack/CallStack.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/stack/CallStack.kt new file mode 100644 index 00000000000..0b3cbc9e696 --- /dev/null +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/stack/CallStack.kt @@ -0,0 +1,201 @@ +/* + * Copyright 2010-2021 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.ir.interpreter.stack + +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.interpreter.CompoundInstruction +import org.jetbrains.kotlin.ir.interpreter.Instruction +import org.jetbrains.kotlin.ir.interpreter.SimpleInstruction +import org.jetbrains.kotlin.ir.interpreter.state.State +import org.jetbrains.kotlin.ir.interpreter.state.StateWithClosure +import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.util.fileOrNull + +internal class CallStack { + private val frames = mutableListOf() + private fun getCurrentFrame() = frames.last() + internal fun getCurrentFrameOwner() = frames.last().currentSubFrameOwner + + fun newFrame(frameOwner: IrElement, instructions: List, irFile: IrFile? = null) { + val newFrame = SubFrame(instructions.toMutableList(), frameOwner) + frames.add(Frame(newFrame, irFile)) + } + + fun newFrame(frameOwner: IrFunction, instructions: List) { + val newFrame = SubFrame(instructions.toMutableList(), frameOwner) + frames.add(Frame(newFrame, frameOwner.fileOrNull)) + } + + fun newSubFrame(frameOwner: IrElement, instructions: List) { + val newFrame = SubFrame(instructions.toMutableList(), frameOwner) + getCurrentFrame().addSubFrame(newFrame) + } + + fun dropFrame() { + frames.removeLast() + } + + fun dropFrameAndCopyResult() { + val result = peekState() ?: return dropFrame() + popState() + dropFrame() + pushState(result) + } + + fun dropSubFrame() { + getCurrentFrame().removeSubFrame() + } + + fun returnFromFrameWithResult(irReturn: IrReturn) { + val result = popState() + var frameOwner = getCurrentFrameOwner() + while (frameOwner != irReturn.returnTargetSymbol.owner) { + when (frameOwner) { + is IrTry -> { + dropSubFrame() + pushState(result) + addInstruction(SimpleInstruction(irReturn)) + addInstruction(CompoundInstruction(frameOwner.finallyExpression)) + return + } + is IrCatch -> { + val tryBlock = getCurrentFrame().dropInstructions()!!.element as IrTry// last instruction in `catch` block is `try` + dropSubFrame() + pushState(result) + addInstruction(SimpleInstruction(irReturn)) + addInstruction(CompoundInstruction(tryBlock.finallyExpression)) + return + } + else -> { + dropSubFrame() + if (getCurrentFrame().hasNoSubFrames() && frameOwner != irReturn.returnTargetSymbol.owner) dropFrame() + frameOwner = getCurrentFrameOwner() + } + } + } + + dropFrame() + // check that last frame is not a function itself; use case for proxyInterpret + if (frames.size == 0) newFrame(irReturn, emptyList()) // just stub frame + pushState(result) + } + + fun unrollInstructionsForBreakContinue(breakOrContinue: IrBreakContinue) { + var frameOwner = getCurrentFrameOwner() + while (frameOwner != breakOrContinue.loop) { + when (frameOwner) { + is IrTry -> { + getCurrentFrame().removeSubFrameWithoutDataPropagation() + addInstruction(CompoundInstruction(breakOrContinue)) + newSubFrame(frameOwner, listOf(SimpleInstruction(frameOwner))) // will be deleted when interpret 'try' + return + } + is IrCatch -> { + val tryInstruction = getCurrentFrame().dropInstructions()!! // last instruction in `catch` block is `try` + getCurrentFrame().removeSubFrameWithoutDataPropagation() + addInstruction(CompoundInstruction(breakOrContinue)) + newSubFrame(tryInstruction.element!!, listOf(tryInstruction)) // will be deleted when interpret 'try' + return + } + else -> { + getCurrentFrame().removeSubFrameWithoutDataPropagation() + frameOwner = getCurrentFrameOwner() + } + } + } + + when (breakOrContinue) { + is IrBreak -> getCurrentFrame().removeSubFrameWithoutDataPropagation() // drop loop + else -> if (breakOrContinue.loop is IrDoWhileLoop) { + addInstruction(SimpleInstruction(breakOrContinue.loop)) + addInstruction(CompoundInstruction(breakOrContinue.loop.condition)) + } else { + addInstruction(CompoundInstruction(breakOrContinue.loop)) + } + } + } + + fun dropFramesUntilTryCatch() { + val exception = popState() + var frameOwner = getCurrentFrameOwner() + while (frames.isNotEmpty()) { + val frame = getCurrentFrame() + while (!frame.hasNoSubFrames()) { + frameOwner = frame.currentSubFrameOwner + when (frameOwner) { + is IrTry -> { + dropSubFrame() // drop all instructions that left + newSubFrame(frameOwner, listOf()) + addInstruction(SimpleInstruction(frameOwner)) // to evaluate finally at the end + frameOwner.catches.reversed().forEach { addInstruction(CompoundInstruction(it)) } + pushState(exception) + return + } + is IrCatch -> { + // in case of exception in catch, drop everything except of last `try` instruction + addInstruction(frame.dropInstructions()!!) + pushState(exception) + return + } + else -> frame.removeSubFrameWithoutDataPropagation() + } + } + dropFrame() + } + + if (frames.size == 0) newFrame(frameOwner, emptyList()) // just stub frame + pushState(exception) + } + + fun hasNoInstructions() = frames.isEmpty() || (frames.size == 1 && frames.first().hasNoInstructions()) + + fun addInstruction(instruction: Instruction) { + getCurrentFrame().addInstruction(instruction) + } + + fun popInstruction(): Instruction { + return getCurrentFrame().popInstruction() + } + + fun pushState(state: State) { + getCurrentFrame().pushState(state) + } + + fun popState(): State = getCurrentFrame().popState() + fun peekState(): State? = getCurrentFrame().peekState() + + fun addVariable(variable: Variable) { + getCurrentFrame().addVariable(variable) + } + + fun getVariable(symbol: IrSymbol): Variable = getCurrentFrame().getVariable(symbol) + + fun storeUpValues(state: StateWithClosure) { + // TODO save only necessary declarations + state.upValues.addAll(getCurrentFrame().getAll().toMutableList()) + } + + fun loadUpValues(state: StateWithClosure) { + state.upValues.forEach { addVariable(it) } + } + + fun copyUpValuesFromPreviousFrame() { + frames[frames.size - 2].getAll().forEach { addVariable(it) } + } + + fun getStackTrace(): List { + return frames.map { it.toString() }.filter { it != Frame.NOT_DEFINED } + } + + fun getFileAndPositionInfo(): String { + return frames[frames.size - 2].getFileAndPositionInfo() + } + + fun getStackCount(): Int = frames.size +} diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/stack/Frame.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/stack/Frame.kt index 351379933ef..74cea11691d 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/stack/Frame.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/stack/Frame.kt @@ -1,80 +1,143 @@ /* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2021 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.ir.interpreter.stack -import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterEmptyReturnStackError +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.interpreter.Instruction +import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterError import org.jetbrains.kotlin.ir.interpreter.state.State import org.jetbrains.kotlin.ir.symbols.IrSymbol -import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol +import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable +import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly +import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult -internal interface Frame { - fun addVar(variable: Variable) - fun addAll(variables: List) - fun getVariable(symbol: IrSymbol): Variable? - fun getAll(): List - fun contains(symbol: IrSymbol): Boolean - fun pushReturnValue(state: State) - fun pushReturnValue(frame: Frame) // TODO rename to getReturnValueFrom - fun peekReturnValue(): State - fun popReturnValue(): State - fun hasReturnValue(): Boolean +internal class Frame(subFrame: SubFrame, val irFile: IrFile? = null) { + private val innerStack = mutableListOf(subFrame) + private var currentInstruction: Instruction? = null + val currentSubFrameOwner: IrElement + get() = getCurrentFrame().owner + + companion object { + const val NOT_DEFINED = "Not defined" + } + + private fun getCurrentFrame() = innerStack.last() + + fun addSubFrame(frame: SubFrame) { + innerStack.add(frame) + } + + fun removeSubFrame() { + getCurrentFrame().peekState()?.let { if (innerStack.size > 1) innerStack[innerStack.size - 2].pushState(it) } + removeSubFrameWithoutDataPropagation() + } + + fun removeSubFrameWithoutDataPropagation() { + innerStack.removeLast() + } + + fun hasNoSubFrames() = innerStack.isEmpty() + fun hasNoInstructions() = hasNoSubFrames() || (innerStack.size == 1 && innerStack.first().isEmpty()) + + fun addInstruction(instruction: Instruction) { + getCurrentFrame().pushInstruction(instruction) + } + + fun popInstruction(): Instruction { + return getCurrentFrame().popInstruction().apply { currentInstruction = this } + } + + fun dropInstructions() = getCurrentFrame().dropInstructions() + + fun pushState(state: State) { + getCurrentFrame().pushState(state) + } + + fun popState(): State = getCurrentFrame().popState() + fun peekState(): State? = getCurrentFrame().peekState() + + fun addVariable(variable: Variable) { + getCurrentFrame().addVariable(variable) + } + + fun getVariable(symbol: IrSymbol): Variable { + return innerStack.firstNotNullResult { it.getVariable(symbol) } + ?: throw InterpreterError("$symbol not found") // TODO better message + } + + fun getAll(): List = innerStack.flatMap { it.getAll() } + + private fun getLineNumberForCurrentInstruction(): String { + irFile ?: return "" + val frameOwner = currentInstruction?.element + return when { + frameOwner is IrExpression || (frameOwner is IrDeclaration && frameOwner.origin == IrDeclarationOrigin.DEFINED) -> + ":${irFile.fileEntry.getLineNumber(frameOwner.startOffset) + 1}" + else -> "" + } + } + + fun getFileAndPositionInfo(): String { + irFile ?: return NOT_DEFINED + val lineNum = getLineNumberForCurrentInstruction() + return "${irFile.name}$lineNum" + } + + override fun toString(): String { + irFile ?: return NOT_DEFINED + val fileNameCapitalized = irFile.name.replace(".kt", "Kt").capitalizeAsciiOnly() + val entryPoint = innerStack.firstOrNull { it.owner is IrFunction }?.owner as? IrFunction + val lineNum = getLineNumberForCurrentInstruction() + + return "at $fileNameCapitalized.${entryPoint?.fqNameWhenAvailable ?: ""}(${irFile.name}$lineNum)" + } } -// TODO replace exceptions with InterpreterException -internal class InterpreterFrame( - private val pool: MutableList = mutableListOf(), - private val typeArguments: List = listOf() -) : Frame { - private val returnStack: MutableList = mutableListOf() +internal class SubFrame(private val instructions: MutableList, val owner: IrElement) { + private val memory = mutableListOf() + private val dataStack = DataStack() - override fun addVar(variable: Variable) { - pool.add(variable) + fun isEmpty() = instructions.isEmpty() + + fun pushInstruction(instruction: Instruction) { + instructions.add(0, instruction) } - override fun addAll(variables: List) { - pool.addAll(variables) + fun popInstruction(): Instruction { + return instructions.removeFirst() } - override fun getVariable(symbol: IrSymbol): Variable? { - return (if (symbol is IrTypeParameterSymbol) typeArguments else pool).firstOrNull { it.symbol == symbol } + fun dropInstructions() = instructions.lastOrNull()?.apply { instructions.clear() } + + fun pushState(state: State) { + dataStack.push(state) } - override fun getAll(): List { - return pool + fun popState(): State = dataStack.pop() + fun peekState(): State? = if (!dataStack.isEmpty()) dataStack.peek() else null + + fun addVariable(variable: Variable) { + memory += variable } - override fun contains(symbol: IrSymbol): Boolean { - return (typeArguments + pool).any { it.symbol == symbol } - } - - override fun pushReturnValue(state: State) { - returnStack += state - } - - override fun pushReturnValue(frame: Frame) { - if (frame.hasReturnValue()) this.pushReturnValue(frame.popReturnValue()) - } - - override fun hasReturnValue(): Boolean { - return returnStack.isNotEmpty() - } - - override fun peekReturnValue(): State { - if (returnStack.isNotEmpty()) { - return returnStack.last() - } - throw InterpreterEmptyReturnStackError() - } - - override fun popReturnValue(): State { - if (returnStack.isNotEmpty()) { - val item = returnStack.last() - returnStack.removeAt(returnStack.size - 1) - return item - } - throw InterpreterEmptyReturnStackError() - } + fun getVariable(symbol: IrSymbol): Variable? = memory.firstOrNull { it.symbol == symbol } + fun getAll(): List = memory } + +private class DataStack { + private val stack = mutableListOf() + + fun isEmpty() = stack.isEmpty() + + fun push(state: State) { + stack.add(state) + } + + fun pop(): State = stack.removeLast() + fun peek(): State = stack.last() +} \ No newline at end of file diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/stack/Stack.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/stack/Stack.kt deleted file mode 100644 index 53270fcd667..00000000000 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/stack/Stack.kt +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright 2010-2020 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.ir.interpreter.stack - -import org.jetbrains.kotlin.ir.declarations.IrFile -import org.jetbrains.kotlin.ir.declarations.IrFunction -import org.jetbrains.kotlin.ir.declarations.name -import org.jetbrains.kotlin.ir.interpreter.ExecutionResult -import org.jetbrains.kotlin.ir.interpreter.state.State -import org.jetbrains.kotlin.ir.expressions.IrExpression -import org.jetbrains.kotlin.ir.interpreter.exceptions.InterpreterError -import org.jetbrains.kotlin.ir.symbols.IrSymbol -import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol -import org.jetbrains.kotlin.ir.util.fileOrNull -import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable -import org.jetbrains.kotlin.ir.util.isLocal - -internal interface Stack { - fun newFrame(asSubFrame: Boolean = false, initPool: List = listOf(), block: () -> ExecutionResult): ExecutionResult - fun newFrame(irFunction: IrFunction, initPool: List = listOf(), block: () -> ExecutionResult): ExecutionResult - fun newFrame(irFile: IrFile?, initPool: List = listOf(), block: () -> ExecutionResult): ExecutionResult - - fun fixCallEntryPoint(irExpression: IrExpression) - fun getStackTrace(): List - fun getCurrentStackInfo(): String - fun getStackCount(): Int - - fun clean(rootFile: IrFile?) - fun addVar(variable: Variable) - fun addAll(variables: List) - fun getVariable(symbol: IrSymbol): Variable - fun getAll(): List - - fun contains(symbol: IrSymbol): Boolean - fun hasReturnValue(): Boolean - fun pushReturnValue(state: State) - fun popReturnValue(): State - fun peekReturnValue(): State -} - -internal class StackImpl : Stack { - private val frameList = mutableListOf() - private fun getCurrentFrame() = frameList.last() - private var stackCount = 0 - - private fun addNewFrame(asSubFrame: Boolean, initPool: List, irFunction: IrFunction? = null, irFile: IrFile? = null) { - val typeArgumentsPool = initPool.filter { it.symbol is IrTypeParameterSymbol } - val valueArguments = initPool.filter { it.symbol !is IrTypeParameterSymbol } - val newFrame = InterpreterFrame(valueArguments.toMutableList(), typeArgumentsPool) - when { - asSubFrame -> getCurrentFrame().addSubFrame(newFrame) - else -> frameList.add(FrameContainer(irFile, irFunction, newFrame)) - } - } - - private fun withStackIncrement(asSubFrame: Boolean, block: () -> ExecutionResult): ExecutionResult { - return try { - stackCount++ - block() - } finally { - stackCount-- - if (asSubFrame) getCurrentFrame().removeSubFrame() else removeLastFrame() - } - } - - override fun newFrame(asSubFrame: Boolean, initPool: List, block: () -> ExecutionResult): ExecutionResult { - addNewFrame(asSubFrame, initPool, null, null) - return withStackIncrement(asSubFrame, block) - } - - override fun newFrame(irFunction: IrFunction, initPool: List, block: () -> ExecutionResult): ExecutionResult { - val asSubFrame = irFunction.isInline || irFunction.isLocal - addNewFrame(asSubFrame, initPool, irFunction, irFunction.fileOrNull) - return withStackIncrement(asSubFrame, block) - } - - override fun newFrame(irFile: IrFile?, initPool: List, block: () -> ExecutionResult): ExecutionResult { - addNewFrame(false, initPool, null, irFile) - return withStackIncrement(false, block) - } - - private fun removeLastFrame() { - if (frameList.size > 1 && getCurrentFrame().hasReturnValue()) frameList[frameList.lastIndex - 1].pushReturnValue(getCurrentFrame()) - frameList.removeAt(frameList.lastIndex) - } - - override fun fixCallEntryPoint(irExpression: IrExpression) { - val fileEntry = getCurrentFrame().irFile?.fileEntry ?: return - val lineNum = fileEntry.getLineNumber(irExpression.startOffset) + 1 - getCurrentFrame().lineNumber = lineNum - } - - override fun getStackTrace(): List { - // TODO implement some sort of cache - return frameList.map { it.toString() } - } - - override fun getCurrentStackInfo(): String { - val frame = getCurrentFrame() - frame.irFile ?: return "Not defined" - val lineNum = if (frame.lineNumber != -1) ":${frame.lineNumber}" else "" - return "${frame.irFile.name}$lineNum" - } - - override fun getStackCount(): Int = stackCount - - override fun clean(rootFile: IrFile?) { - stackCount = 0 - frameList.clear() - frameList.add(FrameContainer(rootFile)) - } - - override fun addVar(variable: Variable) { - getCurrentFrame().addVar(variable) - } - - override fun addAll(variables: List) { - getCurrentFrame().addAll(variables) - } - - override fun getVariable(symbol: IrSymbol): Variable { - return getCurrentFrame().getVariable(symbol) - } - - override fun getAll(): List { - return getCurrentFrame().getAll() - } - - override fun contains(symbol: IrSymbol): Boolean { - return getCurrentFrame().contains(symbol) - } - - override fun hasReturnValue(): Boolean { - return getCurrentFrame().hasReturnValue() - } - - override fun pushReturnValue(state: State) { - getCurrentFrame().pushReturnValue(state) - } - - override fun popReturnValue(): State { - return getCurrentFrame().popReturnValue() - } - - override fun peekReturnValue(): State { - return getCurrentFrame().peekReturnValue() - } -} - -private class FrameContainer(val irFile: IrFile? = null, private val entryPoint: IrFunction? = null, current: Frame = InterpreterFrame()) { - var lineNumber: Int = -1 - private val innerStack = mutableListOf(current) - private fun getTopFrame() = innerStack.first() - - fun addSubFrame(frame: Frame) { - innerStack.add(0, frame) - } - - fun removeSubFrame() { - if (getTopFrame().hasReturnValue() && innerStack.size > 1) innerStack[1].pushReturnValue(getTopFrame()) - innerStack.removeAt(0) - } - - fun addVar(variable: Variable) = getTopFrame().addVar(variable) - fun addAll(variables: List) = getTopFrame().addAll(variables) - fun getAll() = innerStack.flatMap { it.getAll() } - fun getVariable(symbol: IrSymbol): Variable { - return innerStack.firstNotNullOfOrNull { it.getVariable(symbol) } - ?: throw InterpreterError("$symbol not found") // TODO better message - } - - fun contains(symbol: IrSymbol) = innerStack.any { it.contains(symbol) } - fun hasReturnValue() = getTopFrame().hasReturnValue() - fun pushReturnValue(container: FrameContainer) = getTopFrame().pushReturnValue(container.getTopFrame()) - fun pushReturnValue(state: State) = getTopFrame().pushReturnValue(state) - fun popReturnValue() = getTopFrame().popReturnValue() - fun peekReturnValue() = getTopFrame().peekReturnValue() - - override fun toString(): String { - irFile ?: return "Not defined" - val fileNameCapitalized = irFile.name.replace(".kt", "Kt").capitalize() - val lineNum = if (lineNumber != -1) ":$lineNumber" else "" - return "at $fileNameCapitalized.${entryPoint?.fqNameWhenAvailable ?: ""}(${irFile.name}$lineNum)" - } -} diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/Common.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/Common.kt index 455aa48713c..178dddcf5e7 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/Common.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/Common.kt @@ -12,7 +12,8 @@ import org.jetbrains.kotlin.ir.interpreter.stack.Variable import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization import org.jetbrains.kotlin.ir.util.nameForIrSerialization -internal class Common private constructor(override val irClass: IrClass, override val fields: MutableList) : Complex { +internal class Common private constructor(override val irClass: IrClass, override val fields: MutableList) : Complex, StateWithClosure { + override val upValues: MutableList = mutableListOf() override var superWrapperClass: Wrapper? = null override var outerClass: Variable? = null diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/ExceptionState.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/ExceptionState.kt index 5ac4270d47b..fe2c16856f0 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/ExceptionState.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/ExceptionState.kt @@ -17,8 +17,8 @@ import kotlin.math.min internal class ExceptionState private constructor( override val irClass: IrClass, override val fields: MutableList, stackTrace: List -) : Complex, Throwable() { - +) : Complex, StateWithClosure, Throwable() { + override val upValues: MutableList = mutableListOf() override var superWrapperClass: Wrapper? = null override var outerClass: Variable? = null diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/State.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/State.kt index 202ea09042f..a44882801ed 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/State.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/State.kt @@ -9,7 +9,8 @@ import org.jetbrains.kotlin.ir.interpreter.stack.Variable import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.expressions.IrCall -import org.jetbrains.kotlin.ir.interpreter.exceptions.throwAsUserException +import org.jetbrains.kotlin.ir.interpreter.IrInterpreterEnvironment +import org.jetbrains.kotlin.ir.interpreter.handleUserException import org.jetbrains.kotlin.ir.symbols.IrSymbol import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.defaultType @@ -64,11 +65,12 @@ internal fun State.isSubtypeOf(other: IrType): Boolean { * This method used to check if for not null parameter there was passed null argument. */ internal fun State.checkNullability( - irType: IrType?, throwException: () -> Nothing = { NullPointerException().throwAsUserException() } -): State { + irType: IrType?, environment: IrInterpreterEnvironment, exceptionToThrow: () -> Throwable = { NullPointerException() } +): State? { if (irType !is IrSimpleType) return this if (this.isNull() && !irType.isNullable()) { - throwException() + exceptionToThrow().handleUserException(environment) + return null } return this } \ No newline at end of file diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/StateWithClosure.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/StateWithClosure.kt new file mode 100644 index 00000000000..3f7e012b40f --- /dev/null +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/StateWithClosure.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2010-2021 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.ir.interpreter.state + +import org.jetbrains.kotlin.ir.interpreter.stack.Variable + +internal interface StateWithClosure { + val upValues: MutableList +} \ No newline at end of file diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/reflection/KFunctionState.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/reflection/KFunctionState.kt index 67cc094cff4..a7a5eb97258 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/reflection/KFunctionState.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/state/reflection/KFunctionState.kt @@ -7,25 +7,23 @@ package org.jetbrains.kotlin.ir.interpreter.state.reflection import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrFunction -import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.descriptors.IrAbstractFunctionFactory import org.jetbrains.kotlin.ir.expressions.IrCall import org.jetbrains.kotlin.ir.expressions.IrFunctionReference import org.jetbrains.kotlin.ir.interpreter.IrInterpreter -import org.jetbrains.kotlin.ir.interpreter.getLastOverridden import org.jetbrains.kotlin.ir.interpreter.proxy.reflection.KParameterProxy import org.jetbrains.kotlin.ir.interpreter.proxy.reflection.KTypeParameterProxy import org.jetbrains.kotlin.ir.interpreter.proxy.reflection.KTypeProxy import org.jetbrains.kotlin.ir.interpreter.stack.Variable +import org.jetbrains.kotlin.ir.interpreter.state.StateWithClosure import org.jetbrains.kotlin.ir.types.classOrNull -import org.jetbrains.kotlin.ir.util.nameForIrSerialization import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.utils.addToStdlib.cast import kotlin.reflect.KParameter import kotlin.reflect.KType import kotlin.reflect.KTypeParameter -internal class KFunctionState(val irFunction: IrFunction, override val irClass: IrClass) : ReflectionState() { +internal class KFunctionState(val irFunction: IrFunction, override val irClass: IrClass) : ReflectionState(), StateWithClosure { + override val upValues: MutableList = mutableListOf() override val fields: MutableList = mutableListOf() private var _parameters: List? = null private var _returnType: KType? = null