From 6ba8fbd45195ef041d70cb2aba6961e3fda8e726 Mon Sep 17 00:00:00 2001 From: Roman Artemev Date: Mon, 9 Dec 2019 16:54:34 +0300 Subject: [PATCH] [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 --- .../optimizations/FoldConstantLowering.kt | 136 +++++++++++++----- .../kotlin/ir/backend/js/JsLoweringPhases.kt | 2 +- .../testData/codegen/box/constants/float.kt | 26 +++- .../codegen/box/intrinsics/tostring.kt | 1 - .../codegen/box/primitiveTypes/kt2269.kt | 2 +- 5 files changed, 129 insertions(+), 38 deletions(-) diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/optimizations/FoldConstantLowering.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/optimizations/FoldConstantLowering.kt index 289dd5962c0..6ec651d8186 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/optimizations/FoldConstantLowering.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/optimizations/FoldConstantLowering.kt @@ -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() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt index 64a336d5705..df9eeeda2eb 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt @@ -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) diff --git a/compiler/testData/codegen/box/constants/float.kt b/compiler/testData/codegen/box/constants/float.kt index 5c60d382df1..a8d051ab51b 100644 --- a/compiler/testData/codegen/box/constants/float.kt +++ b/compiler/testData/codegen/box/constants/float.kt @@ -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" +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/intrinsics/tostring.kt b/compiler/testData/codegen/box/intrinsics/tostring.kt index cfacc542f57..6c121a8f9d6 100644 --- a/compiler/testData/codegen/box/intrinsics/tostring.kt +++ b/compiler/testData/codegen/box/intrinsics/tostring.kt @@ -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" diff --git a/compiler/testData/codegen/box/primitiveTypes/kt2269.kt b/compiler/testData/codegen/box/primitiveTypes/kt2269.kt index 19e8d98acb0..7e35c39f193 100644 --- a/compiler/testData/codegen/box/primitiveTypes/kt2269.kt +++ b/compiler/testData/codegen/box/primitiveTypes/kt2269.kt @@ -1,4 +1,4 @@ -// IGNORE_BACKEND_FIR: JVM_IR + fun box() : String { 230?.toByte()?.hashCode() 9.hashCode()