Move out intrinsic evaluation from interpreter class

This commit is contained in:
Ivan Kylchik
2020-04-14 18:58:30 +03:00
parent 6eae77572a
commit eb3ac74c3f
5 changed files with 258 additions and 106 deletions
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.backend.common.interpreter.builtins.*
import org.jetbrains.kotlin.backend.common.interpreter.exceptions.InterpreterException
import org.jetbrains.kotlin.backend.common.interpreter.exceptions.InterpreterMethodNotFoundException
import org.jetbrains.kotlin.backend.common.interpreter.exceptions.InterpreterTimeOutException
import org.jetbrains.kotlin.backend.common.interpreter.intrinsics.IntrinsicEvaluator
import org.jetbrains.kotlin.backend.common.interpreter.stack.*
import org.jetbrains.kotlin.backend.common.interpreter.state.*
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
@@ -32,8 +33,6 @@ class IrInterpreter(irModule: IrModuleFragment) {
private val irBuiltIns = irModule.irBuiltins
private val irExceptions = irModule.files.flatMap { it.declarations }.filterIsInstance<IrClass>()
.filter { it.isSubclassOf(irBuiltIns.throwableClass.owner) }
private val classCastException = irExceptions.first { it.name.asString() == ClassCastException::class.java.simpleName }
private val illegalArgumentException = irExceptions.first { it.name.asString() == IllegalArgumentException::class.java.simpleName }
private val stack = StackImpl()
private var commandCount = 0
@@ -154,71 +153,7 @@ class IrInterpreter(irModule: IrModuleFragment) {
}
private suspend fun handleIntrinsicMethods(irFunction: IrFunction): ExecutionResult {
when (irFunction.name.asString()) {
"emptyArray" -> {
val result = emptyArray<Any?>()
stack.pushReturnValue(result.toState(result.getType(irFunction.returnType)))
}
"arrayOf" -> {
val result = irFunction.getArgsForMethodInvocation(stack.getAll()).toTypedArray()
stack.pushReturnValue(result.toState(result.getType(irFunction.returnType)))
}
"arrayOfNulls" -> {
val size = stack.getVariableState(irFunction.valueParameters.first().descriptor).asInt()
val result = arrayOfNulls<Any?>(size)
stack.pushReturnValue(result.toState(result.getType(irFunction.returnType)))
}
"values", "enumValues" -> {
val enumClass =
(irFunction.parent as? IrClass) ?: stack.getVariableState(irFunction.typeParameters.first().descriptor).irClass
val enumEntries = enumClass.declarations
.filterIsInstance<IrEnumEntry>()
.map { entry ->
entry.interpret().check { return it }
stack.popReturnValue() as Common
}
stack.pushReturnValue(enumEntries.toTypedArray().toState(irBuiltIns.arrayClass.defaultType))
}
"valueOf", "enumValueOf" -> {
val enumClass =
(irFunction.parent as? IrClass) ?: stack.getVariableState(irFunction.typeParameters.first().descriptor).irClass
val enumEntryName = stack.getVariableState(irFunction.valueParameters.first().descriptor).asString()
val enumEntry = enumClass.declarations
.filterIsInstance<IrEnumEntry>()
.singleOrNull { it.name.asString() == enumEntryName }
if (enumEntry == null) {
val message = "No enum constant ${enumClass.fqNameForIrSerialization}.$enumEntryName"
stack.pushReturnValue(ExceptionState(IllegalArgumentException(message), illegalArgumentException, stack.getStackTrace()))
return Exception
} else {
enumEntry.interpret().check { return it }
}
}
"replace" -> {
val states = stack.getAll().map { it.state }
val regex = states.filterIsInstance<Wrapper>().single().value as Regex
val input = states.filterIsInstance<Primitive<*>>().single().asString()
val transform = states.filterIsInstance<Lambda>().single().irFunction
val matchResultParameter = transform.valueParameters.single()
val result = regex.replace(input) {
val itAsState = Variable(matchResultParameter.descriptor, Wrapper(it, matchResultParameter.type.classOrNull!!.owner))
runBlocking { stack.newFrame(initPool = listOf(itAsState)) { transform.interpret() } }//.check { return it }
stack.popReturnValue().asString()
//TODO("replace not implemented")
}
stack.pushReturnValue(result.toState(irBuiltIns.stringType))
}
"hashCode" -> {
if (irFunction.parentAsClass.isEnumClass) {
calculateBuiltIns(irFunction.getLastOverridden() as IrSimpleFunction)
} else {
throw InterpreterException("Hash code function intrinsic is supported only for enum class")
}
}
else -> throw InterpreterException("Unsupported intrinsic ${irFunction.name}")
}
return Next
return IntrinsicEvaluator().evaluate(irFunction, stack) { this.interpret() }
}
private suspend fun calculateAbstract(irFunction: IrFunction): ExecutionResult {
@@ -419,42 +354,12 @@ class IrInterpreter(irModule: IrModuleFragment) {
val parent = owner.parent as IrClass
if (parent.hasAnnotation(evaluateIntrinsicAnnotation)) {
return when (owner.parentAsClass.getEvaluateIntrinsicValue()) {
"kotlin.Long" -> {
val low = valueArguments[0].state.asInt()
val high = valueArguments[1].state.asInt()
stack.pushReturnValue((high.toLong().shl(32) + low).toState(irBuiltIns.longType))
Next
}
"kotlin.Char" -> {
val value = valueArguments[0].state.asInt()
stack.pushReturnValue(value.toChar().toState(irBuiltIns.charType))
Next
}
else -> stack.newFrame(initPool = valueArguments) { Wrapper.getConstructorMethod(owner).invokeMethod(owner) }
}
return stack.newFrame(initPool = valueArguments) { Wrapper.getConstructorMethod(owner).invokeMethod(owner) }
}
if (parent.defaultType.isArray() || parent.defaultType.isPrimitiveArray()) {
// array constructor doesn't have body so must be treated separately
val arrayConstructor = irBuiltIns.primitiveArrays.first().constructors.single { it.owner.valueParameters.size == 2 }
val sizeDescriptor = arrayConstructor.owner.valueParameters.single { it.name.asString() == "size" }.descriptor
val size = valueArguments[0].state.asInt()
val arrayValue = MutableList<Any>(size) { 0 }
if (owner.valueParameters.size == 2) {
val initDescriptor = arrayConstructor.owner.valueParameters.single { it.name.asString() == "init" }.descriptor
val initLambda = valueArguments[1].state as Lambda
val indexDescriptor = initLambda.irFunction.valueParameters.single().descriptor
for (i in 0 until size) {
stack.newFrame(initPool = listOf(Variable(indexDescriptor, i.toState(irBuiltIns.intType)))) {
initLambda.irFunction.body!!.interpret()
}.check(ReturnLabel.RETURN) { return it } // TODO throw exception if label != RETURN
arrayValue[i] = stack.popReturnValue().let { (it as? Wrapper)?.value ?: (it as? Primitive<*>)?.value ?: it }
}
}
stack.pushReturnValue(arrayValue.toPrimitiveStateArray(parent.defaultType))
return Next
return stack.newFrame(initPool = valueArguments) { handleIntrinsicMethods(owner) }
}
val state = Common(parent)
@@ -665,11 +570,9 @@ class IrInterpreter(irModule: IrModuleFragment) {
IrTypeOperator.IMPLICIT_COERCION_TO_UNIT -> stack.popReturnValue()
IrTypeOperator.CAST, IrTypeOperator.IMPLICIT_CAST -> {
if (!stack.peekReturnValue().irClass.defaultType.isSubtypeOf(expression.type, irBuiltIns)) {
val convertibleClassName = stack.popReturnValue().irClass.fqNameForIrSerialization
val castClassName = expression.type.classOrNull?.owner?.fqNameForIrSerialization
val message = "$convertibleClassName cannot be cast to $castClassName"
stack.pushReturnValue(ExceptionState(ClassCastException(message), classCastException, stack.getStackTrace()))
return Exception
val convertibleClassName = stack.popReturnValue().irClass.fqNameWhenAvailable
val castClassName = expression.type.classOrNull?.owner?.fqNameWhenAvailable
throw ClassCastException("$convertibleClassName cannot be cast to $castClassName")
}
}
IrTypeOperator.SAFE_CAST -> {
@@ -0,0 +1,16 @@
/*
* 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.backend.common.interpreter.intrinsics
import org.jetbrains.kotlin.backend.common.interpreter.ExecutionResult
import org.jetbrains.kotlin.backend.common.interpreter.stack.Stack
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrFunction
interface Intrinsic {
fun equalTo(irFunction: IrFunction): Boolean
suspend fun evaluate(irFunction: IrFunction, stack: Stack, interpret: suspend IrElement.() -> ExecutionResult): ExecutionResult
}
@@ -0,0 +1,29 @@
/*
* 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.backend.common.interpreter.intrinsics
import org.jetbrains.kotlin.backend.common.interpreter.ExecutionResult
import org.jetbrains.kotlin.backend.common.interpreter.exceptions.InterpreterMethodNotFoundException
import org.jetbrains.kotlin.backend.common.interpreter.stack.Stack
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrFunction
class IntrinsicEvaluator {
suspend fun evaluate(irFunction: IrFunction, stack: Stack, interpret: suspend IrElement.() -> ExecutionResult): ExecutionResult {
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)
RegexReplace.equalTo(irFunction) -> RegexReplace.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)
else -> throw InterpreterMethodNotFoundException("Method ${irFunction.name} hasn't implemented")
}
}
}
@@ -0,0 +1,201 @@
/*
* 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.backend.common.interpreter.intrinsics
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.backend.common.interpreter.*
import org.jetbrains.kotlin.backend.common.interpreter.stack.Stack
import org.jetbrains.kotlin.backend.common.interpreter.stack.Variable
import org.jetbrains.kotlin.backend.common.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.types.classOrNull
import org.jetbrains.kotlin.ir.util.*
sealed class IntrinsicBase : Intrinsic
object EmptyArray : IntrinsicBase() {
override fun equalTo(irFunction: IrFunction): Boolean {
val fqName = irFunction.fqNameWhenAvailable.toString()
return fqName == "kotlin.emptyArray"
}
override suspend fun evaluate(
irFunction: IrFunction, stack: Stack, interpret: suspend IrElement.() -> ExecutionResult
): ExecutionResult {
stack.pushReturnValue(emptyArray<Any?>().toState(irFunction.returnType))
return Next
}
}
object ArrayOf : IntrinsicBase() {
override fun equalTo(irFunction: IrFunction): Boolean {
val fqName = irFunction.fqNameWhenAvailable.toString()
return fqName == "kotlin.arrayOf"
}
override suspend fun evaluate(
irFunction: IrFunction, stack: Stack, interpret: suspend IrElement.() -> ExecutionResult
): ExecutionResult {
val array = irFunction.getArgsForMethodInvocation(stack.getAll()).toTypedArray()
stack.pushReturnValue(array.toState(irFunction.returnType))
return Next
}
}
object ArrayOfNulls : IntrinsicBase() {
override fun equalTo(irFunction: IrFunction): Boolean {
val fqName = irFunction.fqNameWhenAvailable.toString()
return fqName == "kotlin.arrayOfNulls"
}
override suspend fun evaluate(
irFunction: IrFunction, stack: Stack, interpret: suspend IrElement.() -> ExecutionResult
): ExecutionResult {
val size = stack.getVariableState(irFunction.valueParameters.first().descriptor).asInt()
val array = arrayOfNulls<Any?>(size)
stack.pushReturnValue(array.toState(irFunction.returnType))
return Next
}
}
object EnumValues : IntrinsicBase() {
override fun equalTo(irFunction: IrFunction): Boolean {
val fqName = irFunction.fqNameWhenAvailable.toString()
return (fqName == "kotlin.enumValues" || fqName.endsWith(".values")) && irFunction.valueParameters.isEmpty()
}
override suspend fun evaluate(
irFunction: IrFunction, stack: Stack, interpret: suspend IrElement.() -> ExecutionResult
): ExecutionResult {
val enumClass = when (irFunction.fqNameWhenAvailable.toString()) {
"kotlin.enumValues" -> stack.getVariableState(irFunction.typeParameters.first().descriptor).irClass
else -> irFunction.parent as IrClass
}
val enumEntries = enumClass.declarations.filterIsInstance<IrEnumEntry>()
.map { entry -> entry.interpret().check { return it }.let { stack.popReturnValue() as Common } }
stack.pushReturnValue(enumEntries.toTypedArray().toState(irFunction.returnType))
return Next
}
}
object EnumValueOf : IntrinsicBase() {
override fun equalTo(irFunction: IrFunction): Boolean {
val fqName = irFunction.fqNameWhenAvailable.toString()
return (fqName == "kotlin.enumValueOf" || fqName.endsWith(".valueOf")) && irFunction.valueParameters.size == 1
}
override suspend fun evaluate(
irFunction: IrFunction, stack: Stack, interpret: suspend IrElement.() -> ExecutionResult
): ExecutionResult {
val enumClass = when (irFunction.fqNameWhenAvailable.toString()) {
"kotlin.enumValueOf" -> stack.getVariableState(irFunction.typeParameters.first().descriptor).irClass
else -> irFunction.parent as IrClass
}
val enumEntryName = stack.getVariableState(irFunction.valueParameters.first().descriptor).asString()
val enumEntry = enumClass.declarations.filterIsInstance<IrEnumEntry>().singleOrNull { it.name.asString() == enumEntryName }
enumEntry?.interpret()?.check { return it }
?: throw IllegalArgumentException("No enum constant ${enumClass.fqNameWhenAvailable}.$enumEntryName")
return Next
}
}
object RegexReplace : IntrinsicBase() {
override fun equalTo(irFunction: IrFunction): Boolean {
val fqName = irFunction.fqNameWhenAvailable.toString()
return fqName == "kotlin.text.Regex.replace" && irFunction.valueParameters.size == 2
}
override suspend fun evaluate(
irFunction: IrFunction, stack: Stack, interpret: suspend IrElement.() -> ExecutionResult
): ExecutionResult {
val states = stack.getAll().map { it.state }
val regex = states.filterIsInstance<Wrapper>().single().value as Regex
val input = states.filterIsInstance<Primitive<*>>().single().asString()
val transform = states.filterIsInstance<Lambda>().single().irFunction
val matchResultParameter = transform.valueParameters.single()
val result = regex.replace(input) {
val itAsState = Variable(matchResultParameter.descriptor, Wrapper(it, matchResultParameter.type.classOrNull!!.owner))
runBlocking { stack.newFrame(initPool = listOf(itAsState)) { transform.interpret() } }//.check { return it }
stack.popReturnValue().asString()
}
stack.pushReturnValue(result.toState(irFunction.returnType))
return Next
}
}
object EnumHashCode : IntrinsicBase() {
override fun equalTo(irFunction: IrFunction): Boolean {
val fqName = irFunction.fqNameWhenAvailable.toString()
return fqName.endsWith(".hashCode") && irFunction.parentAsClass.isEnumClass
}
override suspend fun evaluate(
irFunction: IrFunction, stack: Stack, interpret: suspend IrElement.() -> ExecutionResult
): ExecutionResult {
val hashCode = stack.getAll().single().state.hashCode()
stack.pushReturnValue(hashCode.toState(irFunction.returnType))
return Next
}
}
object JsPrimitives : IntrinsicBase() {
override fun equalTo(irFunction: IrFunction): Boolean {
val fqName = irFunction.fqNameWhenAvailable.toString()
return fqName == "kotlin.Long.<init>" || fqName == "kotlin.Char.<init>"
}
override suspend fun evaluate(
irFunction: IrFunction, stack: Stack, interpret: suspend IrElement.() -> ExecutionResult
): ExecutionResult {
when (irFunction.fqNameWhenAvailable.toString()) {
"kotlin.Long.<init>" -> {
val low = stack.getVariableState(irFunction.valueParameters[0].descriptor).asInt()
val high = stack.getVariableState(irFunction.valueParameters[1].descriptor).asInt()
stack.pushReturnValue((high.toLong().shl(32) + low).toState(irFunction.returnType))
}
"kotlin.Char.<init>" -> {
val value = stack.getVariableState(irFunction.valueParameters[0].descriptor).asInt()
stack.pushReturnValue(value.toChar().toState(irFunction.returnType))
}
}
return Next
}
}
object ArrayConstructor : IntrinsicBase() {
override fun equalTo(irFunction: IrFunction): Boolean {
val fqName = irFunction.fqNameWhenAvailable.toString()
return fqName.matches("kotlin\\.(Byte|Char|Short|Int|Long|Float|Double|Boolean|)Array\\.<init>".toRegex())
}
override suspend fun evaluate(
irFunction: IrFunction, stack: Stack, interpret: suspend IrElement.() -> ExecutionResult
): ExecutionResult {
val sizeDescriptor = irFunction.valueParameters[0].descriptor
val size = stack.getVariableState(sizeDescriptor).asInt()
val arrayValue = MutableList<Any>(size) { 0 }
if (irFunction.valueParameters.size == 2) {
val initDescriptor = irFunction.valueParameters[1].descriptor
val initLambda = stack.getVariableState(initDescriptor) as Lambda
val index = initLambda.irFunction.valueParameters.single()
for (i in 0 until size) {
val indexVar = listOf(Variable(index.descriptor, i.toState(index.type)))
// TODO throw exception if label != RETURN
stack.newFrame(initPool = indexVar) { initLambda.irFunction.body!!.interpret() }.check(ReturnLabel.RETURN) { return it }
arrayValue[i] = stack.popReturnValue().let { (it as? Wrapper)?.value ?: (it as? Primitive<*>)?.value ?: it }
}
}
stack.pushReturnValue(arrayValue.toPrimitiveStateArray(irFunction.parentAsClass.defaultType))
return Next
}
}
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization
import org.jetbrains.kotlin.ir.util.isTypeParameter
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.name.FqNameUnsafe
import java.lang.invoke.MethodHandle
import java.lang.invoke.MethodHandles
@@ -63,9 +64,11 @@ class Wrapper private constructor(
return Wrapper(objectValue, irClass)
}
fun getConstructorMethod(irConstructor: IrFunction): MethodHandle {
val methodType = irConstructor.getMethodType()
fun getConstructorMethod(irConstructor: IrFunction): MethodHandle? {
val intrinsicValue = irConstructor.parentAsClass.getEvaluateIntrinsicValue()
if (intrinsicValue == "kotlin.Char" || intrinsicValue == "kotlin.Long") return null
val methodType = irConstructor.getMethodType()
return MethodHandles.lookup().findConstructor(irConstructor.returnType.getClass(true), methodType)
}