From eb3ac74c3fdb8a09d93cfcf8df5c4c1965ed1f5e Mon Sep 17 00:00:00 2001 From: Ivan Kylchik Date: Tue, 14 Apr 2020 18:58:30 +0300 Subject: [PATCH] Move out intrinsic evaluation from interpreter class --- .../common/interpreter/IrInterpreter.kt | 111 +--------- .../interpreter/intrinsics/Intrinsic.kt | 16 ++ .../intrinsics/IntrinsicEvaluator.kt | 29 +++ .../intrinsics/IntrinsicImplementations.kt | 201 ++++++++++++++++++ .../common/interpreter/state/Wrapper.kt | 7 +- 5 files changed, 258 insertions(+), 106 deletions(-) create mode 100644 compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/intrinsics/Intrinsic.kt create mode 100644 compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/intrinsics/IntrinsicEvaluator.kt create mode 100644 compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/intrinsics/IntrinsicImplementations.kt diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/IrInterpreter.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/IrInterpreter.kt index 1775fc061c3..4895d1c0a09 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/IrInterpreter.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/IrInterpreter.kt @@ -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() .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() - 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(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() - .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() - .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().single().value as Regex - val input = states.filterIsInstance>().single().asString() - val transform = states.filterIsInstance().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(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 -> { diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/intrinsics/Intrinsic.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/intrinsics/Intrinsic.kt new file mode 100644 index 00000000000..8fe3e5a5738 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/intrinsics/Intrinsic.kt @@ -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 +} \ No newline at end of file diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/intrinsics/IntrinsicEvaluator.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/intrinsics/IntrinsicEvaluator.kt new file mode 100644 index 00000000000..716f368e1e9 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/intrinsics/IntrinsicEvaluator.kt @@ -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") + } + } +} \ No newline at end of file diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/intrinsics/IntrinsicImplementations.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/intrinsics/IntrinsicImplementations.kt new file mode 100644 index 00000000000..8bf360b7da4 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/intrinsics/IntrinsicImplementations.kt @@ -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().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(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() + .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().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().single().value as Regex + val input = states.filterIsInstance>().single().asString() + val transform = states.filterIsInstance().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." || fqName == "kotlin.Char." + } + + override suspend fun evaluate( + irFunction: IrFunction, stack: Stack, interpret: suspend IrElement.() -> ExecutionResult + ): ExecutionResult { + when (irFunction.fqNameWhenAvailable.toString()) { + "kotlin.Long." -> { + 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." -> { + 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\\.".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(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 + } +} \ No newline at end of file diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/state/Wrapper.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/state/Wrapper.kt index 8730bce9915..1a0aa8170b4 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/state/Wrapper.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/interpreter/state/Wrapper.kt @@ -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) }