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