[IR BE] Refactored FoldConstantLowering
- Fix `toString` evaluation for unsigned types in FoldConstantLowering - make corner cases around float/double evaluation work for K/JS - remove usage of kotlin type
This commit is contained in:
+103
-33
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrStringConcatenationImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||
import org.jetbrains.kotlin.ir.types.*
|
||||
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
|
||||
import org.jetbrains.kotlin.ir.util.isUnsigned
|
||||
@@ -20,9 +21,10 @@ import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||
import org.jetbrains.kotlin.resolve.constants.evaluate.evaluateBinary
|
||||
import org.jetbrains.kotlin.resolve.constants.evaluate.evaluateUnary
|
||||
import kotlin.math.floor
|
||||
|
||||
val foldConstantLoweringPhase = makeIrFilePhase(
|
||||
::FoldConstantLowering,
|
||||
{ ctx: CommonBackendContext -> FoldConstantLowering(ctx) },
|
||||
name = "FoldConstantLowering",
|
||||
description = "Constant Folding"
|
||||
)
|
||||
@@ -34,7 +36,10 @@ val foldConstantLoweringPhase = makeIrFilePhase(
|
||||
*
|
||||
* TODO: constant fields (e.g. Double.NaN)
|
||||
*/
|
||||
class FoldConstantLowering(private val context: CommonBackendContext) : IrElementTransformerVoid(), FileLoweringPass {
|
||||
class FoldConstantLowering(
|
||||
private val context: CommonBackendContext,
|
||||
// In K/JS Float and Double are the same so Float constant should be fold similar to Double
|
||||
private val floatSpecial: Boolean = false) : IrElementTransformerVoid(), FileLoweringPass {
|
||||
/**
|
||||
* ID of an binary operator / method.
|
||||
*
|
||||
@@ -90,37 +95,77 @@ class FoldConstantLowering(private val context: CommonBackendContext) : IrElemen
|
||||
}
|
||||
}
|
||||
|
||||
private fun fromFloatConstSafe(call: IrCall, v: Any): IrExpression {
|
||||
if (!floatSpecial) return call.run {
|
||||
IrConstImpl.float(startOffset, endOffset, type, v as Float)
|
||||
}
|
||||
|
||||
return call.run {
|
||||
when (v) {
|
||||
is Float -> IrConstImpl.float(startOffset, endOffset, type, v)
|
||||
is Double -> IrConstImpl.double(startOffset, endOffset, type, v)
|
||||
else -> error("Unexpected constant type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildIrConstant(call: IrCall, v: Any): IrExpression {
|
||||
val constType = call.type.makeNotNull()
|
||||
return when {
|
||||
call.type.isInt() -> IrConstImpl.int(call.startOffset, call.endOffset, call.type, v as Int)
|
||||
call.type.isChar() -> IrConstImpl.char(call.startOffset, call.endOffset, call.type, v as Char)
|
||||
call.type.isBoolean() -> IrConstImpl.boolean(call.startOffset, call.endOffset, call.type, v as Boolean)
|
||||
call.type.isByte() -> IrConstImpl.byte(call.startOffset, call.endOffset, call.type, v as Byte)
|
||||
call.type.isShort() -> IrConstImpl.short(call.startOffset, call.endOffset, call.type, v as Short)
|
||||
call.type.isLong() -> IrConstImpl.long(call.startOffset, call.endOffset, call.type, v as Long)
|
||||
call.type.isDouble() -> IrConstImpl.double(call.startOffset, call.endOffset, call.type, v as Double)
|
||||
call.type.isFloat() -> IrConstImpl.float(call.startOffset, call.endOffset, call.type, v as Float)
|
||||
call.type.isString() -> IrConstImpl.string(call.startOffset, call.endOffset, call.type, v as String)
|
||||
constType.isInt() -> IrConstImpl.int(call.startOffset, call.endOffset, constType, v as Int)
|
||||
constType.isChar() -> IrConstImpl.char(call.startOffset, call.endOffset, constType, v as Char)
|
||||
constType.isBoolean() -> IrConstImpl.boolean(call.startOffset, call.endOffset, constType, v as Boolean)
|
||||
constType.isByte() -> IrConstImpl.byte(call.startOffset, call.endOffset, constType, v as Byte)
|
||||
constType.isShort() -> IrConstImpl.short(call.startOffset, call.endOffset, constType, v as Short)
|
||||
constType.isLong() -> IrConstImpl.long(call.startOffset, call.endOffset, constType, v as Long)
|
||||
constType.isDouble() -> IrConstImpl.double(call.startOffset, call.endOffset, constType, v as Double)
|
||||
constType.isFloat() -> fromFloatConstSafe(call, v)
|
||||
constType.isString() -> IrConstImpl.string(call.startOffset, call.endOffset, constType, v as String)
|
||||
else -> throw IllegalArgumentException("Unexpected IrCall return type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryFoldingUnaryOps(call: IrCall): IrExpression {
|
||||
val operand = call.dispatchReceiver as? IrConst<*> ?: return call
|
||||
val evaluated = evaluateUnary(
|
||||
call.symbol.owner.name.toString(),
|
||||
operand.kind.toString(),
|
||||
operand.value!!
|
||||
) ?: return call
|
||||
val operationName = call.symbol.owner.name.toString()
|
||||
|
||||
val evaluated = when {
|
||||
// Since there is no distinguish between signed and unsigned types a special handling for `toString` is required
|
||||
operationName == "toString" -> constToString(operand)
|
||||
// Disable toFloat folding on K/JS till `toFloat` is fixed (KT-35422)
|
||||
operationName == "toFloat" && floatSpecial -> return call
|
||||
else -> evaluateUnary(
|
||||
operationName,
|
||||
operand.kind.toString(),
|
||||
operand.value!!
|
||||
) ?: return call
|
||||
}
|
||||
|
||||
return buildIrConstant(call, evaluated)
|
||||
}
|
||||
|
||||
private fun coerceToDouble(irConst: IrConst<*>): IrConst<*> {
|
||||
// TODO: for consistency with current K/JS implementation Float constant should be treated as a Double (KT-35422)
|
||||
if (!floatSpecial) return irConst
|
||||
if (irConst.kind == IrConstKind.Float) return irConst.run {
|
||||
IrConstImpl(startOffset, endOffset, context.irBuiltIns.doubleType, IrConstKind.Double, value.toString().toDouble())
|
||||
}
|
||||
return irConst
|
||||
}
|
||||
|
||||
private fun IrType.typeConstructorName(): String {
|
||||
with(this as IrSimpleType) {
|
||||
with(classifier as IrClassSymbol) {
|
||||
return owner.name.asString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryFoldingBinaryOps(call: IrCall): IrExpression {
|
||||
val lhs = call.dispatchReceiver as? IrConst<*> ?: return call
|
||||
val rhs = call.getValueArgument(0) as? IrConst<*> ?: return call
|
||||
val lhs = coerceToDouble(call.dispatchReceiver as? IrConst<*> ?: return call)
|
||||
val rhs = coerceToDouble(call.getValueArgument(0) as? IrConst<*> ?: return call)
|
||||
|
||||
val evaluated = try {
|
||||
fun String.toNonNullable() = if (this.endsWith('?')) this.dropLast(1) else this
|
||||
evaluateBinary(
|
||||
call.symbol.owner.name.toString(),
|
||||
lhs.kind.toString(),
|
||||
@@ -129,7 +174,7 @@ class FoldConstantLowering(private val context: CommonBackendContext) : IrElemen
|
||||
// The passed parameters are guaranteed to be non-null, since they are from IrConst.
|
||||
// 2. The operators are registered with prototype as if virtual member functions. They are identified by
|
||||
// actual_receiver_type.operator_name(parameter_type_in_prototype).
|
||||
call.symbol.owner.valueParameters[0].type.toKotlinType().toString().toNonNullable(),
|
||||
call.symbol.owner.valueParameters[0].type.typeConstructorName(),
|
||||
rhs.value!!
|
||||
) ?: return call
|
||||
} catch (e: Exception) {
|
||||
@@ -159,6 +204,44 @@ class FoldConstantLowering(private val context: CommonBackendContext) : IrElemen
|
||||
return buildIrConstant(call, evaluated)
|
||||
}
|
||||
|
||||
// Unsigned constants are represented through signed constants with a different IrType.
|
||||
private fun constToString(const: IrConst<*>): String {
|
||||
if (floatSpecial) {
|
||||
when (val kind = const.kind) {
|
||||
is IrConstKind.Float -> {
|
||||
val f = kind.valueOf(const)
|
||||
if (!f.isInfinite()) {
|
||||
if (floor(f) == f) {
|
||||
return f.toInt().toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
is IrConstKind.Double -> {
|
||||
val d = kind.valueOf(const)
|
||||
if (!d.isInfinite()) {
|
||||
if (floor(d) == d) {
|
||||
return d.toLong().toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (const.type.isUnsigned()) {
|
||||
when (val kind = const.kind) {
|
||||
is IrConstKind.Byte ->
|
||||
return kind.valueOf(const).toUByte().toString()
|
||||
is IrConstKind.Short ->
|
||||
return kind.valueOf(const).toUShort().toString()
|
||||
is IrConstKind.Int ->
|
||||
return kind.valueOf(const).toUInt().toString()
|
||||
is IrConstKind.Long ->
|
||||
return kind.valueOf(const).toULong().toString()
|
||||
}
|
||||
}
|
||||
return const.value.toString()
|
||||
}
|
||||
|
||||
@ExperimentalUnsignedTypes
|
||||
override fun lower(irFile: IrFile) {
|
||||
irFile.transformChildrenVoid(object : IrElementTransformerVoid() {
|
||||
@@ -174,19 +257,6 @@ class FoldConstantLowering(private val context: CommonBackendContext) : IrElemen
|
||||
}
|
||||
}
|
||||
|
||||
// Unsigned constants are represented through signed constants with a different IrType.
|
||||
private fun constToString(const: IrConst<*>): String {
|
||||
if (const.type.isUnsigned()) {
|
||||
when (val kind = const.kind) {
|
||||
is IrConstKind.Byte -> return kind.valueOf(const).toUByte().toString()
|
||||
is IrConstKind.Short -> return kind.valueOf(const).toUShort().toString()
|
||||
is IrConstKind.Int -> return kind.valueOf(const).toUInt().toString()
|
||||
is IrConstKind.Long -> return kind.valueOf(const).toULong().toString()
|
||||
}
|
||||
}
|
||||
return const.value.toString()
|
||||
}
|
||||
|
||||
override fun visitStringConcatenation(expression: IrStringConcatenation): IrExpression {
|
||||
expression.transformChildrenVoid(this)
|
||||
val folded = mutableListOf<IrExpression>()
|
||||
|
||||
@@ -173,7 +173,7 @@ private val propertyAccessorInlinerLoweringPhase = makeJsModulePhase(
|
||||
)
|
||||
|
||||
private val foldConstantLoweringPhase = makeJsModulePhase(
|
||||
::FoldConstantLowering,
|
||||
{ FoldConstantLowering(it, true) },
|
||||
name = "FoldConstantLowering",
|
||||
description = "[Optimization] Constant Folding",
|
||||
prerequisite = setOf(propertyAccessorInlinerLoweringPhase)
|
||||
|
||||
+24
-2
@@ -1,3 +1,16 @@
|
||||
// KJS_WITH_FULL_RUNTIME
|
||||
// WITH_RUNTIME
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
|
||||
import kotlin.math.*
|
||||
|
||||
fun almostEqual(a: Float, b: Float): Boolean = abs(a - b) < 0.0000001F
|
||||
|
||||
val umax = UInt.MAX_VALUE
|
||||
val ulmax = ULong.MAX_VALUE
|
||||
val imax = Int.MAX_VALUE
|
||||
val lmax = Long.MAX_VALUE
|
||||
|
||||
fun box(): String {
|
||||
if (1F != 1.toFloat()) return "fail 1"
|
||||
if (1.0F != 1.0.toFloat()) return "fail 2"
|
||||
@@ -13,6 +26,15 @@ fun box(): String {
|
||||
if (1e-1f != 1e-1.toFloat()) return "fail 11"
|
||||
if (1.0e-1f != 1.0e-1.toFloat()) return "fail 12"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
if (!almostEqual(kotlin.math.E.toFloat(), exp(1.0F))) return "fail 13"
|
||||
|
||||
if (2147483647.toFloat() != imax.toFloat()) return "fail 14"
|
||||
|
||||
if (9223372036854775807L.toFloat() != lmax.toFloat()) return "fail 15"
|
||||
|
||||
if (0xFFFF_FFFF.toFloat() != umax.toFloat()) return "fail 16"
|
||||
|
||||
if ((2.0f * Long.MAX_VALUE + 1) != ulmax.toFloat()) return "fail 17"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||
// IGNORE_BACKEND: JS
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
|
||||
fun box(): String {
|
||||
if (239.toByte().toString() != (239.toByte() as Byte?).toString()) return "byte failed"
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
|
||||
fun box() : String {
|
||||
230?.toByte()?.hashCode()
|
||||
9.hashCode()
|
||||
|
||||
Reference in New Issue
Block a user