From 057bdb39546d4432c189f5aacc392d09ae22b571 Mon Sep 17 00:00:00 2001 From: Mads Ager Date: Fri, 8 Feb 2019 11:04:13 +0100 Subject: [PATCH] Generate better code for branches based on comparisons. For comparison intrinsics and for instanceof checks, make it possible to get the the stack value produced and branch on that directly instead of materializing a boolean to branch on from it. That reduces code such as ``` IF_CMPEQ L1 CONST_0 GOTO L2 L1: CONST_1 L2: IFEQ L3 ``` to just one IF_CMP instruction. --- .../backend/jvm/codegen/ExpressionCodegen.kt | 50 ++++++++++++++++--- .../backend/jvm/intrinsics/CompareTo.kt | 22 ++++---- .../kotlin/backend/jvm/intrinsics/Equals.kt | 41 ++++++++++----- .../backend/jvm/intrinsics/IntrinsicMethod.kt | 5 ++ .../jvm/intrinsics/IrIntrinsicFunction.kt | 9 +++- .../conditions/negatedZeroCompare.kt | 1 - .../bytecodeText/conditions/zeroCompare.kt | 1 - .../lazyCodegen/negateObjectComp.kt | 1 - .../lazyCodegen/negateObjectCompChaing.kt | 1 - .../alreadyCheckedForIs.kt | 1 - 10 files changed, 96 insertions(+), 36 deletions(-) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt index ca8c405120c..5411455d8af 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.backend.jvm.codegen import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin +import org.jetbrains.kotlin.backend.jvm.intrinsics.ComparisonIntrinsic import org.jetbrains.kotlin.backend.jvm.intrinsics.IrIntrinsicFunction import org.jetbrains.kotlin.backend.jvm.intrinsics.IrIntrinsicMethods import org.jetbrains.kotlin.backend.jvm.lower.CrIrType @@ -717,16 +718,18 @@ class ExpressionCodegen( private fun genConditionWithOptimizationsIfPossible(branch: IrBranch, data: BlockInfo, elseLabel: Label) { var condition = branch.condition var jumpIfFalse = true + + // Instead of materializing a negated value when used for control flow, flip the branch + // targets instead. This significantly cuts down the amount of branches and loads of + // const_0 and const_1 in the generated java bytecode. if (isNegation(condition, classCodegen.context)) { - // Instead of materializing a negated value when used for control flow, flip the branch - // targets instead. This significantly cuts down the amount of branches and loads of - // const_0 and const_1 in the generated java bytecode. condition = negationArgument(condition as IrCall) jumpIfFalse = false } + + // Do not materialize null constants to check for null. Instead use the JVM bytecode + // ifnull and ifnonnull instructions. if (isNullCheck(condition)) { - // Do not materialize null constants to check for null. Instead use the JVM bytecode - // ifnull and ifnonnull instructions. val call = condition as IrCall val left = call.getValueArgument(0)!! val right = call.getValueArgument(1)!! @@ -737,10 +740,41 @@ class ExpressionCodegen( } else { mv.ifnull(elseLabel) } - } else { - gen(condition, data).put(condition.asmType, mv) - BranchedValue.condJump(onStack(condition.asmType), elseLabel, jumpIfFalse, mv) + return } + + // For comparison intrinsics, branch directly based on the comparison instead of + // materializing a boolean and performing and extra jump. + if (condition is IrCall) { + val intrinsic = intrinsics.getIntrinsic(condition.descriptor.original as CallableMemberDescriptor) + if (intrinsic is ComparisonIntrinsic) { + val callable = resolveToCallable(condition, false) + (callable as IrIntrinsicFunction).loadArguments(this, data) + val stackValue = intrinsic.genStackValue(condition, classCodegen.context) + BranchedValue.condJump(stackValue, elseLabel, jumpIfFalse, mv) + return + } + } + + // For instance of type operators, branch directly on the instanceof result instead + // of materializing a boolean and performing an extra jump. + if (condition is IrTypeOperatorCall && + (condition.operator == IrTypeOperator.NOT_INSTANCEOF || condition.operator == IrTypeOperator.INSTANCEOF)) { + val asmType = condition.typeOperand.toKotlinType().asmType + gen(condition.argument, OBJECT_TYPE, data) + val type = boxType(asmType) + generateIsCheck(mv, condition.typeOperand.toKotlinType(), type, state.languageVersionSettings.isReleaseCoroutines()) + val stackValue = + if (condition.operator == IrTypeOperator.INSTANCEOF) + onStack(Type.BOOLEAN_TYPE) + else + StackValue.not(onStack(Type.BOOLEAN_TYPE)) + BranchedValue.condJump(stackValue, elseLabel, jumpIfFalse, mv) + return + } + + gen(condition, data).put(condition.asmType, mv) + BranchedValue.condJump(onStack(condition.asmType), elseLabel, jumpIfFalse, mv) } private fun isNullCheck(expression: IrExpression): Boolean { diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/CompareTo.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/CompareTo.kt index 8f6d18923e4..18f1876e1d5 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/CompareTo.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/CompareTo.kt @@ -63,7 +63,18 @@ class CompareTo : IntrinsicMethod() { class PrimitiveComparison( private val primitiveNumberType: KotlinType, private val operatorToken: KtSingleValueToken -) : IntrinsicMethod() { +) : IntrinsicMethod(), ComparisonIntrinsic { + + override fun genStackValue(expression: IrMemberAccessExpression, context: JvmBackendContext): StackValue { + val parameterType = context.state.typeMapper.mapType(primitiveNumberType) + + return StackValue.cmp( + operatorToken, + parameterType, + StackValue.onStack(parameterType, primitiveNumberType), + StackValue.onStack(parameterType, primitiveNumberType) + ) + } override fun toCallable( expression: IrMemberAccessExpression, @@ -74,14 +85,7 @@ class PrimitiveComparison( return object : IrIntrinsicFunction(expression, signature, context, listOf(parameterType, parameterType)) { override fun genInvokeInstruction(v: InstructionAdapter) { - StackValue - .cmp( - operatorToken, - parameterType, - StackValue.onStack(parameterType, primitiveNumberType), - StackValue.onStack(parameterType, primitiveNumberType) - ) - .put(Type.BOOLEAN_TYPE, v) + genStackValue(expression, context).put(Type.BOOLEAN_TYPE, v) } } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Equals.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Equals.kt index 19e8992440c..a58bb6d52bf 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Equals.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/Equals.kt @@ -22,13 +22,12 @@ import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberOrNullableType import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter -class Equals(val operator: IElementType) : IntrinsicMethod() { +class Equals(val operator: IElementType) : IntrinsicMethod(), ComparisonIntrinsic { - override fun toCallable( + private fun argumentTypes( expression: IrMemberAccessExpression, - signature: JvmMethodSignature, context: JvmBackendContext - ): IrIntrinsicFunction { + ): Pair { val receiverAndArgs = expression.receiverAndArgs().apply { assert(size == 2) { "Equals expects 2 arguments, but ${joinToString()}" } } @@ -40,18 +39,34 @@ class Equals(val operator: IElementType) : IntrinsicMethod() { leftType = boxType(leftType) rightType = boxType(rightType) } + return Pair(leftType, rightType) + } + + override fun genStackValue( + expression: IrMemberAccessExpression, + context: JvmBackendContext + ): StackValue { + val (leftType, rightType) = argumentTypes(expression, context) + val opToken = expression.origin + return if (opToken === IrStatementOrigin.EQEQEQ || opToken === IrStatementOrigin.EXCLEQEQ) { + // TODO: always casting to the type of the left operand in case of primitives looks wrong + val operandType = if (isPrimitive(leftType)) leftType else OBJECT_TYPE + StackValue.cmp(operator, operandType, StackValue.onStack(leftType), StackValue.onStack(rightType)) + } else { + genEqualsForExpressionsOnStack(operator, StackValue.onStack(leftType), StackValue.onStack(rightType)) + } + } + + override fun toCallable( + expression: IrMemberAccessExpression, + signature: JvmMethodSignature, + context: JvmBackendContext + ): IrIntrinsicFunction { + var (leftType, rightType) = argumentTypes(expression, context) return object : IrIntrinsicFunction(expression, signature, context, listOf(leftType, rightType)) { override fun genInvokeInstruction(v: InstructionAdapter) { - val opToken = expression.origin - - val value = if (opToken === IrStatementOrigin.EQEQEQ || opToken === IrStatementOrigin.EXCLEQEQ) { - // TODO: always casting to the type of the left operand in case of primitives looks wrong - val operandType = if (isPrimitive(leftType)) leftType else OBJECT_TYPE - StackValue.cmp(operator, operandType, StackValue.onStack(leftType), StackValue.onStack(rightType)) - } else { - genEqualsForExpressionsOnStack(operator, StackValue.onStack(leftType), StackValue.onStack(rightType)) - } + val value = genStackValue(expression, context) value.put(Type.BOOLEAN_TYPE, v) } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IntrinsicMethod.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IntrinsicMethod.kt index 4ac2a247bec..4936810dfe4 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IntrinsicMethod.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IntrinsicMethod.kt @@ -8,6 +8,7 @@ package org.jetbrains.kotlin.backend.jvm.intrinsics import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.codegen.Callable import org.jetbrains.kotlin.codegen.CallableMethod +import org.jetbrains.kotlin.codegen.StackValue import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression import org.jetbrains.kotlin.ir.types.toKotlinType @@ -15,6 +16,10 @@ import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.commons.Method +interface ComparisonIntrinsic { + fun genStackValue(expression: IrMemberAccessExpression, context: JvmBackendContext): StackValue +} + abstract class IntrinsicMethod { open fun toCallable( diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicFunction.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicFunction.kt index d9ffb9346f5..ad8f0917a98 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicFunction.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicFunction.kt @@ -86,6 +86,14 @@ open class IrIntrinsicFunction( } open fun invoke(v: InstructionAdapter, codegen: ExpressionCodegen, data: BlockInfo): StackValue { + loadArguments(codegen, data) + return StackValue.onStack(genInvokeInstructionWithResult(v)) + } + + fun loadArguments( + codegen: ExpressionCodegen, + data: BlockInfo + ) { val args = listOfNotNull(expression.dispatchReceiver, expression.extensionReceiver) + expression.descriptor.valueParameters.mapIndexed { i, descriptor -> expression.getValueArgument(i) ?: if (descriptor.isVararg) @@ -104,7 +112,6 @@ open class IrIntrinsicFunction( genArg(irExpression, codegen, i, data) } } - return StackValue.onStack(genInvokeInstructionWithResult(v)) } open fun genArg(expression: IrExpression, codegen: ExpressionCodegen, index: Int, data: BlockInfo) { diff --git a/compiler/testData/codegen/bytecodeText/conditions/negatedZeroCompare.kt b/compiler/testData/codegen/bytecodeText/conditions/negatedZeroCompare.kt index 8b09c3036fc..a00f8492543 100644 --- a/compiler/testData/codegen/bytecodeText/conditions/negatedZeroCompare.kt +++ b/compiler/testData/codegen/bytecodeText/conditions/negatedZeroCompare.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR val a = 1 fun main() { diff --git a/compiler/testData/codegen/bytecodeText/conditions/zeroCompare.kt b/compiler/testData/codegen/bytecodeText/conditions/zeroCompare.kt index a1d33acf175..9010e305d5d 100644 --- a/compiler/testData/codegen/bytecodeText/conditions/zeroCompare.kt +++ b/compiler/testData/codegen/bytecodeText/conditions/zeroCompare.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR val a = 1 fun main() { diff --git a/compiler/testData/codegen/bytecodeText/lazyCodegen/negateObjectComp.kt b/compiler/testData/codegen/bytecodeText/lazyCodegen/negateObjectComp.kt index 7f12a19e683..97a4da19884 100644 --- a/compiler/testData/codegen/bytecodeText/lazyCodegen/negateObjectComp.kt +++ b/compiler/testData/codegen/bytecodeText/lazyCodegen/negateObjectComp.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR val p: Int? = 1; val z: Int? = 2; diff --git a/compiler/testData/codegen/bytecodeText/lazyCodegen/negateObjectCompChaing.kt b/compiler/testData/codegen/bytecodeText/lazyCodegen/negateObjectCompChaing.kt index cfc07474e28..697e7bc0927 100644 --- a/compiler/testData/codegen/bytecodeText/lazyCodegen/negateObjectCompChaing.kt +++ b/compiler/testData/codegen/bytecodeText/lazyCodegen/negateObjectCompChaing.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR val p: Int? = 1; val z: Int? = 2; diff --git a/compiler/testData/codegen/bytecodeText/nullCheckOptimization/alreadyCheckedForIs.kt b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/alreadyCheckedForIs.kt index fc5d1e91adf..c2d247d898d 100644 --- a/compiler/testData/codegen/bytecodeText/nullCheckOptimization/alreadyCheckedForIs.kt +++ b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/alreadyCheckedForIs.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR fun test(x: String?) { if (x !is String) return