Fuse primitive equality with safe call to avoid boxing

In code like 'a?.b == 42', we can immediately generate equality
comparison result when receiver is null (false for '==', true for '!='),
since the primitive value is definitely non-null.
Otherwise unnecessary boxing/unboxing is generated to handle possibly
null result of 'a?.b'.
This commit is contained in:
Dmitry Petrov
2017-05-12 12:34:35 +03:00
parent 55498b7f63
commit 495fba43c0
12 changed files with 474 additions and 26 deletions
@@ -209,33 +209,9 @@ class NumberCompare(
right: StackValue
) : BranchedValue(left, right, operandType, NumberCompare.getNumberCompareOpcode(opToken)) {
override fun patchOpcode(opcode: Int, v: InstructionAdapter): Int {
// Opcode takes one int operand from the stack
assert(opcode in IFEQ..IFLE) {
"Opcode for comparing must be in range ${IFEQ..IFLE}, but $opcode was found"
}
override fun patchOpcode(opcode: Int, v: InstructionAdapter): Int =
patchOpcode(opcode, v, opToken, operandType)
return when (operandType) {
Type.FLOAT_TYPE, Type.DOUBLE_TYPE -> {
if (opToken == KtTokens.GT || opToken == KtTokens.GTEQ) {
v.cmpl(operandType)
}
else {
v.cmpg(operandType)
}
opcode
}
Type.LONG_TYPE -> {
v.lcmp()
opcode
}
else -> {
opcode + (IF_ICMPEQ - IFEQ)
}
}
}
companion object {
fun getNumberCompareOpcode(opToken: IElementType): Int {
return when (opToken) {
@@ -250,6 +226,27 @@ class NumberCompare(
}
}
}
fun patchOpcode(opcode: Int, v: InstructionAdapter, opToken: IElementType, operandType: Type): Int {
assert(opcode in IFEQ..IFLE) {
"Opcode for comparing must be in range ${IFEQ..IFLE}, but $opcode was found"
}
return when (operandType) {
Type.FLOAT_TYPE, Type.DOUBLE_TYPE -> {
if (opToken == KtTokens.GT || opToken == KtTokens.GTEQ)
v.cmpl(operandType)
else
v.cmpg(operandType)
opcode
}
Type.LONG_TYPE -> {
v.lcmp()
opcode
}
else ->
opcode + (IF_ICMPEQ - IFEQ)
}
}
}
}
@@ -270,3 +267,91 @@ class ObjectCompare(
}
}
}
abstract class SafeCallFusedWithPrimitiveEqualityBase(
val opToken: IElementType,
operandType: Type,
left: StackValue,
right: StackValue
) : BranchedValue(left, right, operandType, NumberCompare.getNumberCompareOpcode(opToken)) {
private val trueIfEqual = opToken == KtTokens.EQEQ || opToken == KtTokens.EQEQEQ
protected abstract fun cleanupOnNullReceiver(v: InstructionAdapter)
override fun patchOpcode(opcode: Int, v: InstructionAdapter): Int =
NumberCompare.patchOpcode(opcode, v, opToken, operandType)
override fun condJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) {
val endLabel = Label()
arg1.put(operandType, v)
arg2!!.put(operandType, v)
v.visitJumpInsn(patchOpcode(if (jumpIfFalse) opcode else negatedOperations[opcode]!!, v), jumpLabel)
v.goTo(endLabel)
cleanupOnNullReceiver(v)
if (jumpIfFalse == trueIfEqual) {
v.goTo(jumpLabel)
}
v.mark(endLabel)
}
override fun putSelector(type: Type, v: InstructionAdapter) {
val falseLabel = Label()
val endLabel = Label()
arg1.put(operandType, v)
arg2!!.put(operandType, v)
v.visitJumpInsn(patchOpcode(opcode, v), falseLabel)
if (!trueIfEqual) {
val trueLabel = Label()
v.goTo(trueLabel)
cleanupOnNullReceiver(v)
v.mark(trueLabel)
}
v.iconst(1)
v.goTo(endLabel)
if (trueIfEqual) {
cleanupOnNullReceiver(v)
}
v.mark(falseLabel)
v.iconst(0)
v.mark(endLabel)
coerceTo(type, v)
}
}
class SafeCallToPrimitiveEquality(
opToken: IElementType,
operandType: Type,
left: StackValue,
right: StackValue,
val safeReceiverType: Type,
val safeReceiverIsNull: Label
) : SafeCallFusedWithPrimitiveEqualityBase(opToken, operandType, left, right) {
override fun cleanupOnNullReceiver(v: InstructionAdapter) {
v.mark(safeReceiverIsNull)
AsmUtil.pop(v, safeReceiverType)
}
}
class PrimitiveToSafeCallEquality(
opToken: IElementType,
operandType: Type,
left: StackValue,
right: StackValue,
val safeReceiverType: Type,
val safeReceiverIsNull: Label
) : SafeCallFusedWithPrimitiveEqualityBase(opToken, operandType, left, right) {
override fun cleanupOnNullReceiver(v: InstructionAdapter) {
v.mark(safeReceiverIsNull)
AsmUtil.pop(v, safeReceiverType)
AsmUtil.pop(v, arg1.type)
}
}
@@ -2927,6 +2927,13 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
return genCmpWithZero(left, opToken);
}
if (left instanceof KtSafeQualifiedExpression && isPrimitive(rightType)) {
return genCmpSafeCallToPrimitive((KtSafeQualifiedExpression) left, right, rightType, opToken);
}
if (isPrimitive(leftType) && right instanceof KtSafeQualifiedExpression) {
return genCmpPrimitiveToSafeCall(left, leftType, (KtSafeQualifiedExpression) right, opToken);
}
if (isPrimitive(leftType) != isPrimitive(rightType)) {
leftType = boxType(leftType);
rightType = boxType(rightType);
@@ -2941,6 +2948,40 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
return genEqualsForExpressionsPreferIEEE754Arithmetic(left, right, opToken, leftType, rightType, null);
}
private StackValue genCmpPrimitiveToSafeCall(
@NotNull KtExpression left,
@NotNull Type leftType,
@NotNull KtSafeQualifiedExpression right,
@NotNull IElementType opToken
) {
Label rightIsNull = new Label();
return new PrimitiveToSafeCallEquality(
opToken,
leftType,
genLazy(left, leftType),
generateSafeQualifiedExpression(right, rightIsNull),
expressionType(right.getReceiverExpression()),
rightIsNull
);
}
private StackValue genCmpSafeCallToPrimitive(
@NotNull KtSafeQualifiedExpression left,
@NotNull KtExpression right,
@NotNull Type rightType,
@NotNull IElementType opToken
) {
Label leftIsNull = new Label();
return new SafeCallToPrimitiveEquality(
opToken,
rightType,
generateSafeQualifiedExpression(left, leftIsNull),
genLazy(right, rightType),
expressionType(left.getReceiverExpression()),
leftIsNull
);
}
/*tries to use IEEE 754 arithmetic*/
private StackValue genEqualsForExpressionsPreferIEEE754Arithmetic(
@Nullable KtExpression left,