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:
Mads Ager
2019-02-08 11:04:13 +01:00
committed by max-kammerer
parent 7d2c59fb70
commit 057bdb3954
10 changed files with 96 additions and 36 deletions
@@ -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 {
@@ -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)
}
}
}
@@ -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<Type, Type> {
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)
}
}
@@ -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(
@@ -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) {
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
val a = 1
fun main() {
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
val a = 1
fun main() {
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
val p: Int? = 1;
val z: Int? = 2;
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
val p: Int? = 1;
val z: Int? = 2;
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
fun test(x: String?) {
if (x !is String) return