[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:
Roman Artemev
2019-12-09 16:54:34 +03:00
committed by romanart
parent e4f83b96a3
commit 6ba8fbd451
5 changed files with 129 additions and 38 deletions
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrStringConcatenationImpl 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.types.*
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.isUnsigned 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.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.resolve.constants.evaluate.evaluateBinary import org.jetbrains.kotlin.resolve.constants.evaluate.evaluateBinary
import org.jetbrains.kotlin.resolve.constants.evaluate.evaluateUnary import org.jetbrains.kotlin.resolve.constants.evaluate.evaluateUnary
import kotlin.math.floor
val foldConstantLoweringPhase = makeIrFilePhase( val foldConstantLoweringPhase = makeIrFilePhase(
::FoldConstantLowering, { ctx: CommonBackendContext -> FoldConstantLowering(ctx) },
name = "FoldConstantLowering", name = "FoldConstantLowering",
description = "Constant Folding" description = "Constant Folding"
) )
@@ -34,7 +36,10 @@ val foldConstantLoweringPhase = makeIrFilePhase(
* *
* TODO: constant fields (e.g. Double.NaN) * 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. * 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 { private fun buildIrConstant(call: IrCall, v: Any): IrExpression {
val constType = call.type.makeNotNull()
return when { return when {
call.type.isInt() -> IrConstImpl.int(call.startOffset, call.endOffset, call.type, v as Int) constType.isInt() -> IrConstImpl.int(call.startOffset, call.endOffset, constType, v as Int)
call.type.isChar() -> IrConstImpl.char(call.startOffset, call.endOffset, call.type, v as Char) constType.isChar() -> IrConstImpl.char(call.startOffset, call.endOffset, constType, v as Char)
call.type.isBoolean() -> IrConstImpl.boolean(call.startOffset, call.endOffset, call.type, v as Boolean) constType.isBoolean() -> IrConstImpl.boolean(call.startOffset, call.endOffset, constType, v as Boolean)
call.type.isByte() -> IrConstImpl.byte(call.startOffset, call.endOffset, call.type, v as Byte) constType.isByte() -> IrConstImpl.byte(call.startOffset, call.endOffset, constType, v as Byte)
call.type.isShort() -> IrConstImpl.short(call.startOffset, call.endOffset, call.type, v as Short) constType.isShort() -> IrConstImpl.short(call.startOffset, call.endOffset, constType, v as Short)
call.type.isLong() -> IrConstImpl.long(call.startOffset, call.endOffset, call.type, v as Long) constType.isLong() -> IrConstImpl.long(call.startOffset, call.endOffset, constType, v as Long)
call.type.isDouble() -> IrConstImpl.double(call.startOffset, call.endOffset, call.type, v as Double) constType.isDouble() -> IrConstImpl.double(call.startOffset, call.endOffset, constType, v as Double)
call.type.isFloat() -> IrConstImpl.float(call.startOffset, call.endOffset, call.type, v as Float) constType.isFloat() -> fromFloatConstSafe(call, v)
call.type.isString() -> IrConstImpl.string(call.startOffset, call.endOffset, call.type, v as String) constType.isString() -> IrConstImpl.string(call.startOffset, call.endOffset, constType, v as String)
else -> throw IllegalArgumentException("Unexpected IrCall return type") else -> throw IllegalArgumentException("Unexpected IrCall return type")
} }
} }
private fun tryFoldingUnaryOps(call: IrCall): IrExpression { private fun tryFoldingUnaryOps(call: IrCall): IrExpression {
val operand = call.dispatchReceiver as? IrConst<*> ?: return call val operand = call.dispatchReceiver as? IrConst<*> ?: return call
val evaluated = evaluateUnary( val operationName = call.symbol.owner.name.toString()
call.symbol.owner.name.toString(),
operand.kind.toString(), val evaluated = when {
operand.value!! // Since there is no distinguish between signed and unsigned types a special handling for `toString` is required
) ?: return call 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) 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 { private fun tryFoldingBinaryOps(call: IrCall): IrExpression {
val lhs = call.dispatchReceiver as? IrConst<*> ?: return call val lhs = coerceToDouble(call.dispatchReceiver as? IrConst<*> ?: return call)
val rhs = call.getValueArgument(0) as? IrConst<*> ?: return call val rhs = coerceToDouble(call.getValueArgument(0) as? IrConst<*> ?: return call)
val evaluated = try { val evaluated = try {
fun String.toNonNullable() = if (this.endsWith('?')) this.dropLast(1) else this
evaluateBinary( evaluateBinary(
call.symbol.owner.name.toString(), call.symbol.owner.name.toString(),
lhs.kind.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. // 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 // 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). // 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!! rhs.value!!
) ?: return call ) ?: return call
} catch (e: Exception) { } catch (e: Exception) {
@@ -159,6 +204,44 @@ class FoldConstantLowering(private val context: CommonBackendContext) : IrElemen
return buildIrConstant(call, evaluated) 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 @ExperimentalUnsignedTypes
override fun lower(irFile: IrFile) { override fun lower(irFile: IrFile) {
irFile.transformChildrenVoid(object : IrElementTransformerVoid() { 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 { override fun visitStringConcatenation(expression: IrStringConcatenation): IrExpression {
expression.transformChildrenVoid(this) expression.transformChildrenVoid(this)
val folded = mutableListOf<IrExpression>() val folded = mutableListOf<IrExpression>()
@@ -173,7 +173,7 @@ private val propertyAccessorInlinerLoweringPhase = makeJsModulePhase(
) )
private val foldConstantLoweringPhase = makeJsModulePhase( private val foldConstantLoweringPhase = makeJsModulePhase(
::FoldConstantLowering, { FoldConstantLowering(it, true) },
name = "FoldConstantLowering", name = "FoldConstantLowering",
description = "[Optimization] Constant Folding", description = "[Optimization] Constant Folding",
prerequisite = setOf(propertyAccessorInlinerLoweringPhase) prerequisite = setOf(propertyAccessorInlinerLoweringPhase)
+23 -1
View File
@@ -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 { fun box(): String {
if (1F != 1.toFloat()) return "fail 1" if (1F != 1.toFloat()) return "fail 1"
if (1.0F != 1.0.toFloat()) return "fail 2" 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 (1e-1f != 1e-1.toFloat()) return "fail 11"
if (1.0e-1f != 1.0e-1.toFloat()) return "fail 12" if (1.0e-1f != 1.0e-1.toFloat()) return "fail 12"
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" return "OK"
} }
-1
View File
@@ -1,7 +1,6 @@
// IGNORE_BACKEND_FIR: JVM_IR // IGNORE_BACKEND_FIR: JVM_IR
// TODO: muted automatically, investigate should it be ran for JS or not // TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS // IGNORE_BACKEND: JS
// IGNORE_BACKEND: JS_IR
fun box(): String { fun box(): String {
if (239.toByte().toString() != (239.toByte() as Byte?).toString()) return "byte failed" if (239.toByte().toString() != (239.toByte() as Byte?).toString()) return "byte failed"
+1 -1
View File
@@ -1,4 +1,4 @@
// IGNORE_BACKEND_FIR: JVM_IR
fun box() : String { fun box() : String {
230?.toByte()?.hashCode() 230?.toByte()?.hashCode()
9.hashCode() 9.hashCode()