[IR] Rewrite how interpreter works with the Unit result

This way interpreter works in more correct way. Every expression
produces some result, and if we don't need it, the interpreter will
discard it.
This commit is contained in:
Ivan Kylchik
2023-07-12 14:24:18 +02:00
committed by Space Team
parent 6204119219
commit 1eca5d1a3b
3 changed files with 43 additions and 21 deletions
@@ -18,15 +18,12 @@ import org.jetbrains.kotlin.ir.interpreter.exceptions.verify
import org.jetbrains.kotlin.ir.interpreter.stack.CallStack import org.jetbrains.kotlin.ir.interpreter.stack.CallStack
import org.jetbrains.kotlin.ir.interpreter.state.* import org.jetbrains.kotlin.ir.interpreter.state.*
import org.jetbrains.kotlin.ir.types.classOrNull import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.types.isUnit
import org.jetbrains.kotlin.ir.types.makeNullable import org.jetbrains.kotlin.ir.types.makeNullable
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
internal fun IrExpression.handleAndDropResult(callStack: CallStack, dropOnlyUnit: Boolean = false) { internal fun IrExpression.handleAndDropResult(callStack: CallStack) {
val dropResult = fun() { val dropResult = fun() { callStack.popState() }
if (!dropOnlyUnit && !this.type.isUnit() || callStack.peekState().isUnit()) callStack.popState()
}
callStack.pushInstruction(CustomInstruction(dropResult)) callStack.pushInstruction(CustomInstruction(dropResult))
callStack.pushCompoundInstruction(this) callStack.pushCompoundInstruction(this)
} }
@@ -212,6 +209,7 @@ private fun unfoldInstanceInitializerCall(instanceInitializerCall: IrInstanceIni
val toInitialize = irClass.declarations.filter { it is IrProperty || it is IrAnonymousInitializer } val toInitialize = irClass.declarations.filter { it is IrProperty || it is IrAnonymousInitializer }
val state = irClass.thisReceiver?.symbol?.let { callStack.loadState(it) } // try to avoid recalculation of properties val state = irClass.thisReceiver?.symbol?.let { callStack.loadState(it) } // try to avoid recalculation of properties
callStack.pushSimpleInstruction(instanceInitializerCall)
toInitialize.reversed().forEach { toInitialize.reversed().forEach {
when { when {
it is IrAnonymousInitializer -> callStack.pushCompoundInstruction(it.body) it is IrAnonymousInitializer -> callStack.pushCompoundInstruction(it.body)
@@ -258,7 +256,7 @@ private fun unfoldStatements(statements: List<IrStatement>, callStack: CallStack
is IrExpression -> is IrExpression ->
when { when {
i.isLastIndex() -> callStack.pushCompoundInstruction(statement) i.isLastIndex() -> callStack.pushCompoundInstruction(statement)
else -> statement.handleAndDropResult(callStack, dropOnlyUnit = true) else -> statement.handleAndDropResult(callStack)
} }
else -> callStack.pushCompoundInstruction(statement) else -> callStack.pushCompoundInstruction(statement)
} }
@@ -435,7 +433,7 @@ private fun unfoldStringConcatenation(expression: IrStringConcatenation, environ
private fun unfoldComposite(element: IrComposite, callStack: CallStack) { private fun unfoldComposite(element: IrComposite, callStack: CallStack) {
when (element.origin) { when (element.origin) {
IrStatementOrigin.DESTRUCTURING_DECLARATION, IrStatementOrigin.DO_WHILE_LOOP, null -> // is null for body of do while loop IrStatementOrigin.DESTRUCTURING_DECLARATION, IrStatementOrigin.DO_WHILE_LOOP, null -> // is null for body of do while loop
element.statements.reversed().forEach { callStack.pushCompoundInstruction(it) } unfoldStatements(element.statements, callStack)
else -> TODO("${element.origin} not implemented") else -> TODO("${element.origin} not implemented")
} }
} }
@@ -63,7 +63,7 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
callStack.popInstruction().handle() callStack.popInstruction().handle()
} }
return environment.stateToIrExpression(callStack.popState(), expression).apply { callStack.dropFrame() } return environment.stateToIrExpression(extractResultAndAssertThatStackIsEmpty(), expression)
} }
internal fun withNewCallStack(call: IrCall, init: IrInterpreter.() -> Any?): State { internal fun withNewCallStack(call: IrCall, init: IrInterpreter.() -> Any?): State {
@@ -75,10 +75,17 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
callStack.popInstruction().handle() callStack.popInstruction().handle()
} }
callStack.popState().apply { callStack.dropFrame() } extractResultAndAssertThatStackIsEmpty()
} }
} }
private fun extractResultAndAssertThatStackIsEmpty(): State {
val result = callStack.popState()
assert(callStack.peekState() == null)
callStack.dropFrame()
return result
}
private fun interpret(element: IrElement) { private fun interpret(element: IrElement) {
when (element) { when (element) {
is IrSimpleFunction -> interpretFunction(element) is IrSimpleFunction -> interpretFunction(element)
@@ -87,6 +94,7 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
is IrConstructorCall -> interpretConstructorCall(element) is IrConstructorCall -> interpretConstructorCall(element)
is IrEnumConstructorCall -> interpretEnumConstructorCall(element) is IrEnumConstructorCall -> interpretEnumConstructorCall(element)
is IrDelegatingConstructorCall -> interpretDelegatingConstructorCall(element) is IrDelegatingConstructorCall -> interpretDelegatingConstructorCall(element)
is IrInstanceInitializerCall -> callStack.pushState(getUnitState())
is IrValueParameter -> interpretValueParameter(element) is IrValueParameter -> interpretValueParameter(element)
is IrField -> interpretField(element) is IrField -> interpretField(element)
is IrBody -> interpretBody(element) is IrBody -> interpretBody(element)
@@ -98,13 +106,13 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
is IrGetEnumValue -> interpretGetEnumValue(element) is IrGetEnumValue -> interpretGetEnumValue(element)
is IrEnumEntry -> interpretEnumEntry(element) is IrEnumEntry -> interpretEnumEntry(element)
is IrConst<*> -> interpretConst(element) is IrConst<*> -> interpretConst(element)
is IrVariable -> callStack.storeState(element.symbol, callStack.popState()) is IrVariable -> interpretVariable(element)
is IrSetValue -> callStack.rewriteState(element.symbol, callStack.popState()) is IrSetValue -> interpretSetValue(element)
is IrTypeOperatorCall -> interpretTypeOperatorCall(element) is IrTypeOperatorCall -> interpretTypeOperatorCall(element)
is IrBranch -> interpretBranch(element) is IrBranch -> interpretBranch(element)
is IrWhileLoop -> interpretWhile(element) is IrWhileLoop -> interpretWhile(element)
is IrDoWhileLoop -> interpretDoWhile(element) is IrDoWhileLoop -> interpretDoWhile(element)
is IrWhen -> callStack.dropSubFrame() is IrWhen -> interpretWhen(element)
is IrVararg -> interpretVararg(element) is IrVararg -> interpretVararg(element)
is IrTry -> interpretTry(element) is IrTry -> interpretTry(element)
is IrThrow -> interpretThrow(element) is IrThrow -> interpretThrow(element)
@@ -198,15 +206,13 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
receiverState.setField(field.correspondingPropertySymbol!!, callStack.popState()) receiverState.setField(field.correspondingPropertySymbol!!, callStack.popState())
} }
@Suppress("UNUSED_PARAMETER")
private fun interpretBody(body: IrBody) { private fun interpretBody(body: IrBody) {
if (callStack.peekState() == null) callStack.pushState(getUnitState()) // implicit Unit result if (body.statements.isEmpty()) callStack.pushState(getUnitState()) // implicit Unit result
} }
@Suppress("UNUSED_PARAMETER")
private fun interpretBlock(block: IrBlock) { private fun interpretBlock(block: IrBlock) {
callStack.dropSubFrame() callStack.dropSubFrame()
if (callStack.peekState() == null) callStack.pushState(getUnitState()) // implicit Unit result if (block.statements.isEmpty()) callStack.pushState(getUnitState()) // implicit Unit result
} }
private fun interpretConstructor(constructor: IrConstructor) { private fun interpretConstructor(constructor: IrConstructor) {
@@ -267,6 +273,8 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
callInterceptor.interceptConstructor(constructorCall, valueArguments) { callInterceptor.interceptConstructor(constructorCall, valueArguments) {
callStack.pushCompoundInstruction(constructor) callStack.pushCompoundInstruction(constructor)
} }
callStack.pushState(getUnitState())
} }
private fun interpretDelegatingConstructorCall(constructorCall: IrDelegatingConstructorCall) { private fun interpretDelegatingConstructorCall(constructorCall: IrDelegatingConstructorCall) {
@@ -300,6 +308,16 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
callStack.pushState(expression.toPrimitive()) callStack.pushState(expression.toPrimitive())
} }
private fun interpretVariable(variable: IrVariable) {
callStack.storeState(variable.symbol, callStack.popState())
callStack.pushState(getUnitState())
}
private fun interpretSetValue(expression: IrSetValue) {
callStack.rewriteState(expression.symbol, callStack.popState())
callStack.pushState(getUnitState())
}
private fun interpretReturn(expression: IrReturn) { private fun interpretReturn(expression: IrReturn) {
callStack.returnFromFrameWithResult(expression) callStack.returnFromFrameWithResult(expression)
} }
@@ -326,10 +344,18 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
} }
} }
@Suppress("UNUSED_PARAMETER")
private fun interpretWhen(whenExpression: IrWhen) {
// This method is reachable only if none of the branches were selected.
// In that case, we just return `Unit` result that will be dropped later.
callStack.dropSubFrame()
callStack.pushState(getUnitState())
}
private fun interpretBranch(branch: IrBranch) { private fun interpretBranch(branch: IrBranch) {
val result = callStack.popState().asBoolean() val result = callStack.popState().asBoolean()
if (result) { if (result) {
callStack.dropSubFrame() callStack.dropSubFrame() // drop entire `when` expression from frame
callStack.pushCompoundInstruction(branch.result) callStack.pushCompoundInstruction(branch.result)
} }
} }
@@ -338,6 +364,7 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
val receiver = (expression.receiver as IrDeclarationReference).symbol val receiver = (expression.receiver as IrDeclarationReference).symbol
val propertySymbol = expression.symbol.owner.correspondingPropertySymbol!! val propertySymbol = expression.symbol.owner.correspondingPropertySymbol!!
callStack.loadState(receiver).apply { this.setField(propertySymbol, callStack.popState()) } callStack.loadState(receiver).apply { this.setField(propertySymbol, callStack.popState()) }
callStack.pushState(getUnitState())
} }
private fun interpretGetField(expression: IrGetField) { private fun interpretGetField(expression: IrGetField) {
+1 -4
View File
@@ -1,9 +1,7 @@
@CompileTimeCalculation @CompileTimeCalculation
class A { class A {
const val a = <!EVALUATED: `10`!>{ 10 }()<!> // lambda is needed to avoid computions by old frontend
companion object { companion object {
const val static = <!EVALUATED: `-10`!>{ -10 }()<!> const val static = <!EVALUATED: `-10`!>{ -10 }()<!> // lambda is needed to avoid computions by old frontend
fun getStaticNumber(): Int { fun getStaticNumber(): Int {
return Int.MAX_VALUE return Int.MAX_VALUE
@@ -21,7 +19,6 @@ object ObjectWithConst {
fun concat(first: String, second: Any) = "$first$second" fun concat(first: String, second: Any) = "$first$second"
} }
const val num = <!EVALUATED: `10`!>A().a<!>
const val numStatic = <!EVALUATED: `-10`!>A.static<!> const val numStatic = <!EVALUATED: `-10`!>A.static<!>
const val numStaticFromFun = <!EVALUATED: `2147483647`!>A.getStaticNumber()<!> const val numStaticFromFun = <!EVALUATED: `2147483647`!>A.getStaticNumber()<!>
const val valFromObject = <!EVALUATED: `Value in a: 100`!>ObjectWithConst.b<!> const val valFromObject = <!EVALUATED: `Value in a: 100`!>ObjectWithConst.b<!>