[JS] Support evaluation of const intrinsics for K2
#KT-56023 Fixed #KT-51582 Fixed
This commit is contained in:
+1
-1
@@ -20,10 +20,10 @@ import org.jetbrains.kotlin.ir.interpreter.checker.IrConstTransformer
|
||||
class ConstEvaluationLowering(
|
||||
val context: CommonBackendContext,
|
||||
private val suppressErrors: Boolean = context.configuration.getBoolean(CommonConfigurationKeys.IGNORE_CONST_OPTIMIZATION_ERRORS),
|
||||
configuration: IrInterpreterConfiguration = IrInterpreterConfiguration(printOnlyExceptionMessage = true),
|
||||
private val onWarning: (IrFile, IrElement, IrErrorExpression) -> Unit = { _, _, _ -> },
|
||||
private val onError: (IrFile, IrElement, IrErrorExpression) -> Unit = { _, _, _ -> },
|
||||
) : FileLoweringPass {
|
||||
val configuration = IrInterpreterConfiguration(printOnlyExceptionMessage = true)
|
||||
val interpreter = IrInterpreter(IrInterpreterEnvironment(context.irBuiltIns, configuration), emptyMap())
|
||||
|
||||
override fun lower(irFile: IrFile) {
|
||||
|
||||
+10
-2
@@ -151,13 +151,21 @@ internal class DefaultCallInterceptor(override val interpreter: IrInterpreter) :
|
||||
}
|
||||
|
||||
val receiverType = irFunction.dispatchReceiverParameter?.type ?: irFunction.extensionReceiverParameter?.type
|
||||
val argsType = listOfNotNull(receiverType) + irFunction.valueParameters.map { it.type }
|
||||
val argsType = (listOfNotNull(receiverType) + irFunction.valueParameters.map { it.type }).map {
|
||||
// TODO: for consistency with current K/JS implementation Float constant should be treated as a Double (KT-35422)
|
||||
if (environment.configuration.treatFloatInSpecialWay && it.isFloat()) irBuiltIns.doubleType else it
|
||||
}
|
||||
val argsValues = args.wrap(this, irFunction)
|
||||
|
||||
// TODO replace unary, binary, ternary functions with vararg
|
||||
withExceptionHandler(environment) {
|
||||
val result = when (argsType.size) {
|
||||
1 -> interpretUnaryFunction(methodName, argsType[0].getOnlyName(), argsValues[0])
|
||||
1 -> when {
|
||||
methodName == "toString" && environment.configuration.treatFloatInSpecialWay && (argsValues[0] is Double || argsValues[0] is Float) -> {
|
||||
argsValues[0].specialToStringForJs()
|
||||
}
|
||||
else -> interpretUnaryFunction(methodName, argsType[0].getOnlyName(), argsValues[0])
|
||||
}
|
||||
2 -> when (methodName) {
|
||||
"rangeTo" -> return calculateRangeTo(irFunction.returnType, args)
|
||||
else -> interpretBinaryFunction(
|
||||
|
||||
+22
-9
@@ -8,8 +8,10 @@ package org.jetbrains.kotlin.ir.interpreter
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||
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.interpreter.exceptions.InterpreterError
|
||||
import org.jetbrains.kotlin.ir.interpreter.exceptions.handleUserException
|
||||
import org.jetbrains.kotlin.ir.interpreter.exceptions.verify
|
||||
@@ -112,6 +114,19 @@ private fun unfoldConstructor(constructor: IrConstructor, callStack: CallStack)
|
||||
|
||||
private fun unfoldValueParameters(expression: IrFunctionAccessExpression, environment: IrInterpreterEnvironment) {
|
||||
val callStack = environment.callStack
|
||||
val irFunction = expression.symbol.owner
|
||||
|
||||
if (irFunction.name.asString() == "<get-name>" && expression.dispatchReceiver is IrGetEnumValue) {
|
||||
// this optimization allow us to avoid creation of enum object when we try to interpret simple `name` call; see KT-53480
|
||||
callStack.pushSimpleInstruction(expression)
|
||||
callStack.pushSimpleInstruction(irFunction.dispatchReceiverParameter!!)
|
||||
|
||||
val enumEntry = (expression.dispatchReceiver as IrGetEnumValue).symbol.owner
|
||||
callStack.pushState(enumEntry.toState(environment.irBuiltIns))
|
||||
return
|
||||
}
|
||||
// TODO do the same thing but for "KCallable.name" with "this" as receiver
|
||||
|
||||
val hasDefaults = (0 until expression.valueArgumentsCount).any { expression.getValueArgument(it) == null }
|
||||
if (hasDefaults) {
|
||||
environment.getCachedFunction(expression.symbol, fromDelegatingCall = expression is IrDelegatingConstructorCall)?.let {
|
||||
@@ -166,7 +181,6 @@ private fun unfoldValueParameters(expression: IrFunctionAccessExpression, enviro
|
||||
).owner.createCall().apply { environment.irBuiltIns.copyArgs(expression, this) }
|
||||
callStack.pushCompoundInstruction(callToDefault)
|
||||
} else {
|
||||
val irFunction = expression.symbol.owner
|
||||
callStack.pushSimpleInstruction(expression)
|
||||
|
||||
fun IrValueParameter.schedule(arg: IrExpression?) {
|
||||
@@ -277,14 +291,6 @@ private fun unfoldGetEnumValue(expression: IrGetEnumValue, environment: IrInterp
|
||||
val callStack = environment.callStack
|
||||
environment.mapOfEnums[expression.symbol]?.let { return callStack.pushState(it) }
|
||||
|
||||
val frameOwner = callStack.currentFrameOwner
|
||||
if (frameOwner is IrCall && frameOwner.dispatchReceiver is IrGetEnumValue && frameOwner.symbol.owner.name.asString() == "<get-name>") {
|
||||
// this optimization allow us to avoid creation of enum object when we try to interpret simple `name` call; see KT-53480
|
||||
val enumEntry = (frameOwner.dispatchReceiver as IrGetEnumValue).symbol.owner
|
||||
callStack.pushState(enumEntry.toState(environment.irBuiltIns))
|
||||
return
|
||||
}
|
||||
|
||||
callStack.pushSimpleInstruction(expression)
|
||||
val enumEntry = expression.symbol.owner
|
||||
val enumClass = enumEntry.symbol.owner.parentAsClass
|
||||
@@ -389,6 +395,13 @@ private fun unfoldStringConcatenation(expression: IrStringConcatenation, environ
|
||||
// this callback is used to check the need for an explicit toString call
|
||||
val explicitToStringCheck = fun() {
|
||||
when (val state = callStack.peekState()) {
|
||||
is Primitive<*> -> {
|
||||
// This block is not really needed, but this way it is easier to handle `toString` with `treatFloatInSpecialWay` enabled.
|
||||
callStack.popState()
|
||||
val toStringCall = IrCallImpl.fromSymbolOwner(UNDEFINED_OFFSET, UNDEFINED_OFFSET, environment.irBuiltIns.memberToString)
|
||||
callStack.pushSimpleInstruction(toStringCall)
|
||||
callStack.pushState(state)
|
||||
}
|
||||
is Common -> {
|
||||
callStack.popState()
|
||||
// TODO this check can be dropped after serialization introduction
|
||||
|
||||
@@ -293,6 +293,7 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
|
||||
|
||||
return callStack.pushCompoundInstruction(constructorCall)
|
||||
}
|
||||
|
||||
callStack.pushState(expression.toPrimitive())
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -11,12 +11,14 @@ package org.jetbrains.kotlin.ir.interpreter
|
||||
* @param createNonCompileTimeObjects
|
||||
* 'true' - interpreter will construct object and initialize its properties despite the fact it is not marked as compile time;
|
||||
* 'false' - interpreter will create a representation of empty object, that can be used to get const properties
|
||||
* @param treatFloatInSpecialWay interpret float const as if it was a double and avoid `toFloat` evaluation if possible
|
||||
*/
|
||||
// TODO maybe create some sort of builder
|
||||
class IrInterpreterConfiguration(
|
||||
data class IrInterpreterConfiguration(
|
||||
val maxStack: Int = 10_000,
|
||||
val maxCommands: Int = 1_000_000,
|
||||
val createNonCompileTimeObjects: Boolean = false,
|
||||
val printOnlyExceptionMessage: Boolean = false,
|
||||
val collapseStackTraceFromJDK: Boolean = true,
|
||||
val treatFloatInSpecialWay: Boolean = false,
|
||||
)
|
||||
|
||||
+4
-2
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.ir.IrBuiltIns
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrErrorExpressionImpl
|
||||
import org.jetbrains.kotlin.ir.interpreter.proxy.Proxy
|
||||
import org.jetbrains.kotlin.ir.interpreter.stack.CallStack
|
||||
@@ -118,8 +119,9 @@ class IrInterpreterEnvironment(
|
||||
return when (state) {
|
||||
is Primitive<*> ->
|
||||
when {
|
||||
state.value == null -> state.value.toIrConst(type, start, end)
|
||||
type.isPrimitiveType() || type.isString() -> state.value.toIrConst(type, start, end)
|
||||
configuration.treatFloatInSpecialWay && state.value is Float -> IrConstImpl.float(start, end, type, state.value)
|
||||
configuration.treatFloatInSpecialWay && state.value is Double -> IrConstImpl.double(start, end, type, state.value)
|
||||
state.value == null || type.isPrimitiveType() || type.isString() -> state.value.toIrConst(type, start, end)
|
||||
else -> original // TODO support for arrays
|
||||
}
|
||||
is ExceptionState -> {
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.types.Variance
|
||||
import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly
|
||||
import org.jetbrains.kotlin.utils.keysToMap
|
||||
import java.lang.invoke.MethodType
|
||||
import kotlin.math.floor
|
||||
|
||||
val intrinsicConstEvaluationAnnotation = FqName("kotlin.internal.IntrinsicConstEvaluation")
|
||||
val compileTimeAnnotation = FqName("kotlin.CompileTimeCalculation")
|
||||
@@ -131,7 +132,9 @@ fun IrFunctionAccessExpression.getVarargType(index: Int): IrType? {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun IrFunction.getCapitalizedFileName() = this.file.name.replace(".kt", "Kt").capitalizeAsciiOnly()
|
||||
internal fun IrFunction.getCapitalizedFileName(): String {
|
||||
return this.fileOrNull?.name?.replace(".kt", "Kt")?.capitalizeAsciiOnly() ?: "<UNKNOWN>"
|
||||
}
|
||||
|
||||
internal fun IrClass.isSubclassOfThrowable(): Boolean {
|
||||
return generateSequence(this) { irClass ->
|
||||
@@ -314,6 +317,14 @@ internal fun State.unsignedToString(): String {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Any?.specialToStringForJs(): String {
|
||||
return when {
|
||||
this is Float && !this.isInfinite() && floor(this) == this -> this.toInt().toString()
|
||||
this is Double && !this.isInfinite() && floor(this) == this -> this.toLong().toString()
|
||||
else -> this.toString()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun IrEnumEntry.toState(irBuiltIns: IrBuiltIns): Common {
|
||||
val enumClass = this.symbol.owner.parentAsClass
|
||||
val enumEntries = enumClass.declarations.filterIsInstance<IrEnumEntry>()
|
||||
|
||||
+11
-1
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.IrStatement
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.interpreter.IrInterpreterConfiguration
|
||||
import org.jetbrains.kotlin.ir.interpreter.accessesTopLevelOrObjectField
|
||||
import org.jetbrains.kotlin.ir.interpreter.fqName
|
||||
import org.jetbrains.kotlin.ir.interpreter.isAccessToNotNullableObject
|
||||
@@ -18,7 +19,9 @@ import org.jetbrains.kotlin.ir.util.statements
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
|
||||
|
||||
class IrCompileTimeChecker(
|
||||
containingDeclaration: IrElement? = null, private val mode: EvaluationMode = EvaluationMode.WITH_ANNOTATIONS
|
||||
containingDeclaration: IrElement? = null,
|
||||
private val mode: EvaluationMode = EvaluationMode.WITH_ANNOTATIONS,
|
||||
private val interpreterConfiguration: IrInterpreterConfiguration,
|
||||
) : IrElementVisitor<Boolean, Nothing?> {
|
||||
private var contextExpression: IrCall? = null
|
||||
private val visitedStack = mutableListOf<IrElement>().apply { if (containingDeclaration != null) add(containingDeclaration) }
|
||||
@@ -67,6 +70,13 @@ class IrCompileTimeChecker(
|
||||
val owner = expression.symbol.owner
|
||||
if (!mode.canEvaluateFunction(owner, expression)) return false
|
||||
|
||||
// We disable `toFloat` folding on K/JS till `toFloat` is fixed (KT-35422)
|
||||
// This check must be placed here instead of CallInterceptor because we still
|
||||
// want to evaluate (1) `const val` expressions and (2) values in annotations.
|
||||
if (owner.name.asString() == "toFloat" && interpreterConfiguration.treatFloatInSpecialWay) {
|
||||
return super.visitCall(expression, data)
|
||||
}
|
||||
|
||||
return expression.saveContext {
|
||||
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true
|
||||
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true
|
||||
|
||||
+10
-4
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrVarargImpl
|
||||
import org.jetbrains.kotlin.ir.interpreter.IrInterpreter
|
||||
import org.jetbrains.kotlin.ir.interpreter.IrInterpreterConfiguration
|
||||
import org.jetbrains.kotlin.ir.interpreter.isPrimitiveArray
|
||||
import org.jetbrains.kotlin.ir.interpreter.toIrConst
|
||||
import org.jetbrains.kotlin.ir.types.*
|
||||
@@ -49,9 +50,12 @@ class IrConstTransformer(
|
||||
return this
|
||||
}
|
||||
|
||||
private fun IrExpression.canBeInterpreted(containingDeclaration: IrElement? = null): Boolean {
|
||||
private fun IrExpression.canBeInterpreted(
|
||||
containingDeclaration: IrElement? = null,
|
||||
configuration: IrInterpreterConfiguration = interpreter.environment.configuration
|
||||
): Boolean {
|
||||
return try {
|
||||
this.accept(IrCompileTimeChecker(containingDeclaration, mode = mode), null)
|
||||
this.accept(IrCompileTimeChecker(containingDeclaration, mode, configuration), null)
|
||||
} catch (e: Throwable) {
|
||||
if (suppressExceptions) {
|
||||
return false
|
||||
@@ -87,7 +91,9 @@ class IrConstTransformer(
|
||||
val expression = initializer?.expression ?: return declaration
|
||||
if (expression is IrConst<*>) return declaration
|
||||
val isConst = declaration.correspondingPropertySymbol?.owner?.isConst == true
|
||||
if (isConst && expression.canBeInterpreted(declaration)) {
|
||||
if (!isConst) return super.visitField(declaration)
|
||||
|
||||
if (expression.canBeInterpreted(declaration, interpreter.environment.configuration.copy(treatFloatInSpecialWay = false))) {
|
||||
initializer.expression = expression.interpret(failAsError = true)
|
||||
}
|
||||
|
||||
@@ -129,7 +135,7 @@ class IrConstTransformer(
|
||||
}
|
||||
|
||||
private fun IrExpression.transformSingleArg(expectedType: IrType): IrExpression {
|
||||
if (this.canBeInterpreted()) {
|
||||
if (this.canBeInterpreted(configuration = interpreter.environment.configuration.copy(treatFloatInSpecialWay = false))) {
|
||||
return this.interpret(failAsError = true).convertToConstIfPossible(expectedType)
|
||||
} else if (this is IrConstructorCall) {
|
||||
transformAnnotation(this)
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.ir.interpreter.proxy.reflection.ReflectionProxy.Comp
|
||||
import org.jetbrains.kotlin.ir.interpreter.state.*
|
||||
import org.jetbrains.kotlin.ir.interpreter.state.reflection.ReflectionState
|
||||
import org.jetbrains.kotlin.ir.types.isArray
|
||||
import org.jetbrains.kotlin.ir.types.isFloat
|
||||
import java.lang.invoke.MethodType
|
||||
|
||||
internal interface Proxy {
|
||||
@@ -34,6 +35,8 @@ internal fun State.wrap(callInterceptor: CallInterceptor, remainArraysAsIs: Bool
|
||||
is Primitive<*> -> when {
|
||||
this.isNull() -> null
|
||||
this.type.isArray() || this.type.isPrimitiveArray() -> if (remainArraysAsIs) this else this.value
|
||||
// TODO: for consistency with current K/JS implementation Float constant should be treated as a Double (KT-35422)
|
||||
this.type.isFloat() && callInterceptor.environment.configuration.treatFloatInSpecialWay -> this.value.toString().toDouble()
|
||||
else -> this.value
|
||||
}
|
||||
is Common -> this.asProxy(callInterceptor, extendFrom)
|
||||
|
||||
Reference in New Issue
Block a user