[JVM_IR] Avoid safe-call conversions from Byte? and Short? to Int? for

comparisons.

Having those conversions leads to unnecesary boxing and null checks.

This change does it only for JVM in the optimization lowering. It
is unclear to me if the other backends can get away with something
similar.
This commit is contained in:
Mads Ager
2020-05-07 15:09:25 +02:00
committed by Dmitry Petrov
parent 3d5003d476
commit 1f4a3b0d1c
8 changed files with 66 additions and 19 deletions
@@ -28,7 +28,6 @@ import org.jetbrains.kotlin.ir.util.isNullConst
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi2ir.generators.hasNoSideEffects
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.types.isNullable
import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberOrNullableType
@@ -104,12 +103,12 @@ class Equals(val operator: IElementType) : IntrinsicMethod() {
// could be overridden for the object.
if ((opToken == IrStatementOrigin.EQEQ || opToken == IrStatementOrigin.EXCLEQ) &&
((AsmUtil.isIntOrLongPrimitive(leftType) && !AsmUtil.isPrimitive(rightType)) ||
(AsmUtil.isIntOrLongPrimitive(rightType) && AsmUtil.isBoxedTypeOf(leftType, rightType)))
(AsmUtil.isIntOrLongPrimitive(rightType) && AsmUtil.isBoxedPrimitiveType(leftType)))
) {
val leftIsPrimitive = AsmUtil.isIntOrLongPrimitive(leftType)
val primitiveType = if (leftIsPrimitive) leftType else rightType
val nonPrimitiveType = if (leftIsPrimitive) rightType else leftType
val useNullCheck = AsmUtil.isBoxedTypeOf(nonPrimitiveType, primitiveType)
val useNullCheck = AsmUtil.isBoxedPrimitiveType(nonPrimitiveType)
return if (leftIsPrimitive) {
val loadOther = loadOther(a, leftType)
val boxedValue = b.accept(codegen, data).materializedAt(rightType, b.type)
@@ -20,9 +20,7 @@ import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.types.isBoolean
import org.jetbrains.kotlin.ir.types.isNullableAny
import org.jetbrains.kotlin.ir.types.isPrimitiveType
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
@@ -91,6 +89,8 @@ class JvmOptimizationLowering(val context: JvmBackendContext) : FileLoweringPass
override fun visitCall(expression: IrCall, currentClass: IrClass?): IrExpression {
expression.transformChildren(this, currentClass)
removeIntTypeSafeCastsForEquality(expression)
if (expression.symbol.owner.origin == IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR) {
if (currentClass == null) return expression
val simpleFunction = (expression.symbol.owner as? IrSimpleFunction) ?: return expression
@@ -118,6 +118,41 @@ class JvmOptimizationLowering(val context: JvmBackendContext) : FileLoweringPass
return expression
}
private fun IrType.isByteOrShort() = isByte() || isShort()
// For `==` and `!=`, get rid of safe calls to convert `Byte?` or `Short?` to `Int?`.
// For equality, we do not need to perform such conversions as the builtin for equality
// will handle it. Having the safe call leads to unnecessary null checks and boxing.
private fun removeIntTypeSafeCastsForEquality(expression: IrCall) {
if (expression.origin == IrStatementOrigin.EQEQ || expression.origin == IrStatementOrigin.EXCLEQ) {
for (i in 0 until expression.valueArgumentsCount) {
if (expression.getValueArgument(i)!!.type.makeNotNull().isInt()) {
val argument = expression.getValueArgument(i)!!
if (argument is IrBlock && argument.origin == IrStatementOrigin.SAFE_CALL) {
if (argument.statements.size == 2) {
val variable = argument.statements[0]
if (variable is IrVariable && variable.type.makeNotNull().isByteOrShort()) {
val whenExpression = argument.statements[1]
if (whenExpression is IrWhen && whenExpression.branches.size == 2) {
val secondBranch = whenExpression.branches[1]
if (secondBranch is IrElseBranch && secondBranch.result is IrCall) {
val conversion = secondBranch.result as IrCall
if (conversion.symbol.owner.name.asString() == "toInt" &&
conversion.dispatchReceiver is IrGetValue &&
(conversion.dispatchReceiver as IrGetValue).symbol.owner == variable
) {
expression.putValueArgument(i, variable.initializer)
}
}
}
}
}
}
}
}
}
}
private fun optimizePropertyAccess(
expression: IrCall,
accessor: IrSimpleFunction,
@@ -1,5 +1,3 @@
// IGNORE_BACKEND: JVM_IR
fun testBoolean1(a: Boolean?, b: Boolean) = a == b
fun testBoolean2(a: Boolean?, b: Boolean) = a != b
@@ -1,5 +1,3 @@
// IGNORE_BACKEND: JVM_IR
fun testBoolean1(a: Boolean, b: Boolean?) = a == b
fun testBoolean2(a: Boolean, b: Boolean?) = a != b
@@ -1,6 +1,4 @@
// !LANGUAGE: +ProperIeee754Comparisons
// IGNORE_BACKEND: JVM_IR
fun equals3(a: Byte?, b: Byte?) = a != null && b != null && a == b
fun equals4(a: Byte?, b: Byte?) = if (a is Byte && b is Byte) a == b else null!!
@@ -13,9 +11,19 @@ fun less4(a: Byte?, b: Byte?) = if (a is Byte && b is Byte) a < b else true
fun less5(a: Any?, b: Any?) = if (a is Byte && b is Byte) a < b else true
// JVM_TEMPLATES
// 3 Intrinsics\.areEqual
// 0 Intrinsics\.compare
// 4 INVOKEVIRTUAL java/lang/Byte\.byteValue \(\)B
// 2 INVOKEVIRTUAL java/lang/Number\.intValue \(\)I
// 0 IFGE
// 3 IF_ICMPGE
// JVM_IR_TEMPLATES
// 2 Intrinsics\.areEqual
// 0 Intrinsics\.compare
// 4 INVOKEVIRTUAL java/lang/Byte\.byteValue \(\)B
// 4 INVOKEVIRTUAL java/lang/Number\.byteValue \(\)B
// 0 IFGE
// 3 IF_ICMPGE
@@ -1,5 +1,4 @@
// !LANGUAGE: -ProperIeee754Comparisons
// IGNORE_BACKEND: JVM_IR
fun equals3(a: Byte?, b: Byte?) = a != null && b != null && a == b
@@ -21,10 +20,9 @@ fun less5(a: Any?, b: Any?) = if (a is Byte && b is Byte) a < b else true
// 0 IF_ICMPGE
// JVM_IR_TEMPLATES
// 3 Intrinsics\.areEqual
// 2 Intrinsics\.areEqual
// 0 Intrinsics\.compare
// 4 INVOKEVIRTUAL java/lang/Byte\.byteValue \(\)B
// 2 INVOKEVIRTUAL java/lang/Number\.intValue \(\)I
// 4 INVOKEVIRTUAL java/lang/Number\.byteValue \(\)B
// 0 IFGE
// 3 IF_ICMPGE
// 0 IF_ICMPNE
// 3 IF_ICMPGE
@@ -13,6 +13,7 @@ fun less4(a: Short?, b: Short?) = if (a is Short && b is Short) a < b else true
fun less5(a: Any?, b: Any?) = if (a is Short && b is Short) a < b else true
// JVM_TEMPLATES
// 3 Intrinsics\.areEqual
// 0 Intrinsics\.compare
// 4 INVOKEVIRTUAL java/lang/Short\.shortValue \(\)S
@@ -20,3 +21,13 @@ fun less5(a: Any?, b: Any?) = if (a is Short && b is Short) a < b else true
// 0 IFGE
// 3 IF_ICMPGE
// 0 IF_ICMPNE
// JVM_IR_TEMPLATES
// 2 Intrinsics\.areEqual
// 0 Intrinsics\.compare
// 4 INVOKEVIRTUAL java/lang/Short\.shortValue \(\)S
// 4 INVOKEVIRTUAL java/lang/Number\.shortValue \(\)S
// 0 IFGE
// 3 IF_ICMPGE
// 0 IF_ICMPNE
@@ -20,10 +20,10 @@ fun less5(a: Any?, b: Any?) = if (a is Short && b is Short) a < b else true
// 0 IF_ICMPGE
// JVM_IR_TEMPLATES
// 3 Intrinsics\.areEqual
// 2 Intrinsics\.areEqual
// 0 Intrinsics\.compare
// 4 INVOKEVIRTUAL java/lang/Short\.shortValue \(\)S
// 2 INVOKEVIRTUAL java/lang/Number\.intValue \(\)I
// 4 INVOKEVIRTUAL java/lang/Number\.shortValue \(\)S
// 0 IFGE
// 3 IF_ICMPGE
// 0 IF_ICMPNE