[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.lexer.KtTokens
import org.jetbrains.kotlin.psi2ir.generators.hasNoSideEffects import org.jetbrains.kotlin.psi2ir.generators.hasNoSideEffects
import org.jetbrains.kotlin.resolve.jvm.AsmTypes 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.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.types.isNullable import org.jetbrains.kotlin.types.isNullable
import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberOrNullableType import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberOrNullableType
@@ -104,12 +103,12 @@ class Equals(val operator: IElementType) : IntrinsicMethod() {
// could be overridden for the object. // could be overridden for the object.
if ((opToken == IrStatementOrigin.EQEQ || opToken == IrStatementOrigin.EXCLEQ) && if ((opToken == IrStatementOrigin.EQEQ || opToken == IrStatementOrigin.EXCLEQ) &&
((AsmUtil.isIntOrLongPrimitive(leftType) && !AsmUtil.isPrimitive(rightType)) || ((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 leftIsPrimitive = AsmUtil.isIntOrLongPrimitive(leftType)
val primitiveType = if (leftIsPrimitive) leftType else rightType val primitiveType = if (leftIsPrimitive) leftType else rightType
val nonPrimitiveType = if (leftIsPrimitive) rightType else leftType val nonPrimitiveType = if (leftIsPrimitive) rightType else leftType
val useNullCheck = AsmUtil.isBoxedTypeOf(nonPrimitiveType, primitiveType) val useNullCheck = AsmUtil.isBoxedPrimitiveType(nonPrimitiveType)
return if (leftIsPrimitive) { return if (leftIsPrimitive) {
val loadOther = loadOther(a, leftType) val loadOther = loadOther(a, leftType)
val boxedValue = b.accept(codegen, data).materializedAt(rightType, b.type) 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.IrBlockImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.types.isBoolean import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.isNullableAny
import org.jetbrains.kotlin.ir.types.isPrimitiveType
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer 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 { override fun visitCall(expression: IrCall, currentClass: IrClass?): IrExpression {
expression.transformChildren(this, currentClass) expression.transformChildren(this, currentClass)
removeIntTypeSafeCastsForEquality(expression)
if (expression.symbol.owner.origin == IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR) { if (expression.symbol.owner.origin == IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR) {
if (currentClass == null) return expression if (currentClass == null) return expression
val simpleFunction = (expression.symbol.owner as? IrSimpleFunction) ?: return expression val simpleFunction = (expression.symbol.owner as? IrSimpleFunction) ?: return expression
@@ -118,6 +118,41 @@ class JvmOptimizationLowering(val context: JvmBackendContext) : FileLoweringPass
return expression 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( private fun optimizePropertyAccess(
expression: IrCall, expression: IrCall,
accessor: IrSimpleFunction, accessor: IrSimpleFunction,
@@ -1,5 +1,3 @@
// IGNORE_BACKEND: JVM_IR
fun testBoolean1(a: Boolean?, b: Boolean) = a == b fun testBoolean1(a: Boolean?, b: Boolean) = a == b
fun testBoolean2(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 testBoolean1(a: Boolean, b: Boolean?) = a == b
fun testBoolean2(a: Boolean, b: Boolean?) = a != b fun testBoolean2(a: Boolean, b: Boolean?) = a != b
@@ -1,6 +1,4 @@
// !LANGUAGE: +ProperIeee754Comparisons // !LANGUAGE: +ProperIeee754Comparisons
// IGNORE_BACKEND: JVM_IR
fun equals3(a: Byte?, b: Byte?) = a != null && b != null && a == b 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!! 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 fun less5(a: Any?, b: Any?) = if (a is Byte && b is Byte) a < b else true
// JVM_TEMPLATES
// 3 Intrinsics\.areEqual // 3 Intrinsics\.areEqual
// 0 Intrinsics\.compare // 0 Intrinsics\.compare
// 4 INVOKEVIRTUAL java/lang/Byte\.byteValue \(\)B // 4 INVOKEVIRTUAL java/lang/Byte\.byteValue \(\)B
// 2 INVOKEVIRTUAL java/lang/Number\.intValue \(\)I // 2 INVOKEVIRTUAL java/lang/Number\.intValue \(\)I
// 0 IFGE // 0 IFGE
// 3 IF_ICMPGE // 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 // !LANGUAGE: -ProperIeee754Comparisons
// IGNORE_BACKEND: JVM_IR
fun equals3(a: Byte?, b: Byte?) = a != null && b != null && a == b 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 // 0 IF_ICMPGE
// JVM_IR_TEMPLATES // JVM_IR_TEMPLATES
// 3 Intrinsics\.areEqual // 2 Intrinsics\.areEqual
// 0 Intrinsics\.compare // 0 Intrinsics\.compare
// 4 INVOKEVIRTUAL java/lang/Byte\.byteValue \(\)B // 4 INVOKEVIRTUAL java/lang/Byte\.byteValue \(\)B
// 2 INVOKEVIRTUAL java/lang/Number\.intValue \(\)I // 4 INVOKEVIRTUAL java/lang/Number\.byteValue \(\)B
// 0 IFGE // 0 IFGE
// 3 IF_ICMPGE // 3 IF_ICMPGE
// 0 IF_ICMPNE
@@ -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 fun less5(a: Any?, b: Any?) = if (a is Short && b is Short) a < b else true
// JVM_TEMPLATES
// 3 Intrinsics\.areEqual // 3 Intrinsics\.areEqual
// 0 Intrinsics\.compare // 0 Intrinsics\.compare
// 4 INVOKEVIRTUAL java/lang/Short\.shortValue \(\)S // 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 // 0 IFGE
// 3 IF_ICMPGE // 3 IF_ICMPGE
// 0 IF_ICMPNE // 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 // 0 IF_ICMPGE
// JVM_IR_TEMPLATES // JVM_IR_TEMPLATES
// 3 Intrinsics\.areEqual // 2 Intrinsics\.areEqual
// 0 Intrinsics\.compare // 0 Intrinsics\.compare
// 4 INVOKEVIRTUAL java/lang/Short\.shortValue \(\)S // 4 INVOKEVIRTUAL java/lang/Short\.shortValue \(\)S
// 2 INVOKEVIRTUAL java/lang/Number\.intValue \(\)I // 4 INVOKEVIRTUAL java/lang/Number\.shortValue \(\)S
// 0 IFGE // 0 IFGE
// 3 IF_ICMPGE // 3 IF_ICMPGE
// 0 IF_ICMPNE // 0 IF_ICMPNE