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.
This commit is contained in:
+42
-8
@@ -6,6 +6,7 @@
|
|||||||
package org.jetbrains.kotlin.backend.jvm.codegen
|
package org.jetbrains.kotlin.backend.jvm.codegen
|
||||||
|
|
||||||
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
|
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.IrIntrinsicFunction
|
||||||
import org.jetbrains.kotlin.backend.jvm.intrinsics.IrIntrinsicMethods
|
import org.jetbrains.kotlin.backend.jvm.intrinsics.IrIntrinsicMethods
|
||||||
import org.jetbrains.kotlin.backend.jvm.lower.CrIrType
|
import org.jetbrains.kotlin.backend.jvm.lower.CrIrType
|
||||||
@@ -717,16 +718,18 @@ class ExpressionCodegen(
|
|||||||
private fun genConditionWithOptimizationsIfPossible(branch: IrBranch, data: BlockInfo, elseLabel: Label) {
|
private fun genConditionWithOptimizationsIfPossible(branch: IrBranch, data: BlockInfo, elseLabel: Label) {
|
||||||
var condition = branch.condition
|
var condition = branch.condition
|
||||||
var jumpIfFalse = true
|
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)) {
|
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)
|
condition = negationArgument(condition as IrCall)
|
||||||
jumpIfFalse = false
|
jumpIfFalse = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do not materialize null constants to check for null. Instead use the JVM bytecode
|
||||||
|
// ifnull and ifnonnull instructions.
|
||||||
if (isNullCheck(condition)) {
|
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 call = condition as IrCall
|
||||||
val left = call.getValueArgument(0)!!
|
val left = call.getValueArgument(0)!!
|
||||||
val right = call.getValueArgument(1)!!
|
val right = call.getValueArgument(1)!!
|
||||||
@@ -737,10 +740,41 @@ class ExpressionCodegen(
|
|||||||
} else {
|
} else {
|
||||||
mv.ifnull(elseLabel)
|
mv.ifnull(elseLabel)
|
||||||
}
|
}
|
||||||
} else {
|
return
|
||||||
gen(condition, data).put(condition.asmType, mv)
|
|
||||||
BranchedValue.condJump(onStack(condition.asmType), elseLabel, jumpIfFalse, mv)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
private fun isNullCheck(expression: IrExpression): Boolean {
|
||||||
|
|||||||
+13
-9
@@ -63,7 +63,18 @@ class CompareTo : IntrinsicMethod() {
|
|||||||
class PrimitiveComparison(
|
class PrimitiveComparison(
|
||||||
private val primitiveNumberType: KotlinType,
|
private val primitiveNumberType: KotlinType,
|
||||||
private val operatorToken: KtSingleValueToken
|
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(
|
override fun toCallable(
|
||||||
expression: IrMemberAccessExpression,
|
expression: IrMemberAccessExpression,
|
||||||
@@ -74,14 +85,7 @@ class PrimitiveComparison(
|
|||||||
|
|
||||||
return object : IrIntrinsicFunction(expression, signature, context, listOf(parameterType, parameterType)) {
|
return object : IrIntrinsicFunction(expression, signature, context, listOf(parameterType, parameterType)) {
|
||||||
override fun genInvokeInstruction(v: InstructionAdapter) {
|
override fun genInvokeInstruction(v: InstructionAdapter) {
|
||||||
StackValue
|
genStackValue(expression, context).put(Type.BOOLEAN_TYPE, v)
|
||||||
.cmp(
|
|
||||||
operatorToken,
|
|
||||||
parameterType,
|
|
||||||
StackValue.onStack(parameterType, primitiveNumberType),
|
|
||||||
StackValue.onStack(parameterType, primitiveNumberType)
|
|
||||||
)
|
|
||||||
.put(Type.BOOLEAN_TYPE, v)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,13 +22,12 @@ import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberOrNullableType
|
|||||||
import org.jetbrains.org.objectweb.asm.Type
|
import org.jetbrains.org.objectweb.asm.Type
|
||||||
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
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,
|
expression: IrMemberAccessExpression,
|
||||||
signature: JvmMethodSignature,
|
|
||||||
context: JvmBackendContext
|
context: JvmBackendContext
|
||||||
): IrIntrinsicFunction {
|
): Pair<Type, Type> {
|
||||||
val receiverAndArgs = expression.receiverAndArgs().apply {
|
val receiverAndArgs = expression.receiverAndArgs().apply {
|
||||||
assert(size == 2) { "Equals expects 2 arguments, but ${joinToString()}" }
|
assert(size == 2) { "Equals expects 2 arguments, but ${joinToString()}" }
|
||||||
}
|
}
|
||||||
@@ -40,18 +39,34 @@ class Equals(val operator: IElementType) : IntrinsicMethod() {
|
|||||||
leftType = boxType(leftType)
|
leftType = boxType(leftType)
|
||||||
rightType = boxType(rightType)
|
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)) {
|
return object : IrIntrinsicFunction(expression, signature, context, listOf(leftType, rightType)) {
|
||||||
override fun genInvokeInstruction(v: InstructionAdapter) {
|
override fun genInvokeInstruction(v: InstructionAdapter) {
|
||||||
val opToken = expression.origin
|
val value = genStackValue(expression, context)
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
value.put(Type.BOOLEAN_TYPE, v)
|
value.put(Type.BOOLEAN_TYPE, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.backend.jvm.intrinsics
|
|||||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||||
import org.jetbrains.kotlin.codegen.Callable
|
import org.jetbrains.kotlin.codegen.Callable
|
||||||
import org.jetbrains.kotlin.codegen.CallableMethod
|
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.IrExpression
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
|
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
|
||||||
import org.jetbrains.kotlin.ir.types.toKotlinType
|
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.Type
|
||||||
import org.jetbrains.org.objectweb.asm.commons.Method
|
import org.jetbrains.org.objectweb.asm.commons.Method
|
||||||
|
|
||||||
|
interface ComparisonIntrinsic {
|
||||||
|
fun genStackValue(expression: IrMemberAccessExpression, context: JvmBackendContext): StackValue
|
||||||
|
}
|
||||||
|
|
||||||
abstract class IntrinsicMethod {
|
abstract class IntrinsicMethod {
|
||||||
|
|
||||||
open fun toCallable(
|
open fun toCallable(
|
||||||
|
|||||||
+8
-1
@@ -86,6 +86,14 @@ open class IrIntrinsicFunction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
open fun invoke(v: InstructionAdapter, codegen: ExpressionCodegen, data: BlockInfo): StackValue {
|
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) +
|
val args = listOfNotNull(expression.dispatchReceiver, expression.extensionReceiver) +
|
||||||
expression.descriptor.valueParameters.mapIndexed { i, descriptor ->
|
expression.descriptor.valueParameters.mapIndexed { i, descriptor ->
|
||||||
expression.getValueArgument(i) ?: if (descriptor.isVararg)
|
expression.getValueArgument(i) ?: if (descriptor.isVararg)
|
||||||
@@ -104,7 +112,6 @@ open class IrIntrinsicFunction(
|
|||||||
genArg(irExpression, codegen, i, data)
|
genArg(irExpression, codegen, i, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return StackValue.onStack(genInvokeInstructionWithResult(v))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun genArg(expression: IrExpression, codegen: ExpressionCodegen, index: Int, data: BlockInfo) {
|
open fun genArg(expression: IrExpression, codegen: ExpressionCodegen, index: Int, data: BlockInfo) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
val a = 1
|
val a = 1
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
val a = 1
|
val a = 1
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
val p: Int? = 1;
|
val p: Int? = 1;
|
||||||
val z: Int? = 2;
|
val z: Int? = 2;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
val p: Int? = 1;
|
val p: Int? = 1;
|
||||||
val z: Int? = 2;
|
val z: Int? = 2;
|
||||||
|
|
||||||
|
|||||||
-1
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
fun test(x: String?) {
|
fun test(x: String?) {
|
||||||
if (x !is String) return
|
if (x !is String) return
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user