JVM_IR: Fix null-constant comparison with primitive types.

This commit is contained in:
Kristoffer Andersen
2019-09-05 11:25:17 +02:00
committed by max-kammerer
parent e21da3a61a
commit e7d0909979
10 changed files with 122 additions and 44 deletions
@@ -13,12 +13,14 @@ import org.jetbrains.kotlin.codegen.intrinsics.Not
import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrVariable import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockImpl import org.jetbrains.kotlin.ir.expressions.impl.IrBlockImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.types.isBoolean import org.jetbrains.kotlin.ir.types.isBoolean
import org.jetbrains.kotlin.ir.types.isNullableAny
import org.jetbrains.kotlin.ir.types.isPrimitiveType import org.jetbrains.kotlin.ir.types.isPrimitiveType
import org.jetbrains.kotlin.ir.types.toKotlinType import org.jetbrains.kotlin.ir.types.toKotlinType
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.*
@@ -43,56 +45,71 @@ class JvmBuiltinOptimizationLowering(val context: JvmBackendContext) : FileLower
return expression.type.isPrimitiveType() && (expression is IrConst<*> || expression is IrGetValue) return expression.type.isPrimitiveType() && (expression is IrConst<*> || expression is IrGetValue)
} }
private fun isNullCheckOfPrimitiveTypeValue(call: IrCall, context: JvmBackendContext): Boolean { private val IrFunction.isObjectEquals
if (call.symbol == context.irBuiltIns.eqeqSymbol) { get() = name.asString() == "equals" &&
val left = call.getValueArgument(0)!! valueParameters.count() == 1 &&
val right = call.getValueArgument(1)!! valueParameters[0].type.isNullableAny() &&
// When used for null checks, it is safe to eliminate constants and local variable loads. extensionReceiverParameter == null &&
// Even if a local variable of simple type is updated via the debugger it still cannot dispatchReceiverParameter != null
// be null.
return (right.isNullConst() && left.type.unboxInlineClass().isPrimitiveType())
|| (left.isNullConst() && right.type.unboxInlineClass().isPrimitiveType())
}
return false
}
private fun isNullCheckOfConstant(call: IrCall, context: JvmBackendContext): Boolean {
private fun getOperandsIfCallToEqeqOrEquals(call: IrCall): Pair<IrExpression, IrExpression>? {
if (call.symbol == context.irBuiltIns.eqeqSymbol) { if (call.symbol == context.irBuiltIns.eqeqSymbol) {
val left = call.getValueArgument(0)!! val left = call.getValueArgument(0)!!
val right = call.getValueArgument(1)!! val right = call.getValueArgument(1)!!
return (right.isNullConst() && left is IrConst<*>) return left to right
|| (left.isNullConst() && right is IrConst<*>) } else if (call.symbol.owner.isObjectEquals) {
val left = call.dispatchReceiver!!
val right = call.getValueArgument(0)!!
return left to right
} else {
return null;
} }
return false
} }
override fun lower(irFile: IrFile) { override fun lower(irFile: IrFile) {
irFile.transformChildrenVoid(object : IrElementTransformerVoid() { irFile.transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitCall(expression: IrCall): IrExpression { override fun visitCall(expression: IrCall): IrExpression {
expression.transformChildrenVoid(this) expression.transformChildrenVoid(this)
return if (isNegation(expression, context) && isNegation(expression.dispatchReceiver!!, context)) {
(expression.dispatchReceiver as IrCall).dispatchReceiver!! if (isNegation(expression, context) && isNegation(expression.dispatchReceiver!!, context)) {
} else if (isNullCheckOfPrimitiveTypeValue(expression, context)) { return (expression.dispatchReceiver as IrCall).dispatchReceiver!!
val left = expression.getValueArgument(0)!!
val nonNullArgument = if (left.isNullConst()) expression.getValueArgument(1)!! else left
val constFalse = IrConstImpl.constFalse(expression.startOffset, expression.endOffset, context.irBuiltIns.booleanType)
if (hasNoSideEffectsForNullCompare(nonNullArgument)) {
constFalse
} else {
IrBlockImpl(expression.startOffset, expression.endOffset, expression.type, expression.origin).apply {
statements.add(nonNullArgument.coerceToUnitIfNeeded(nonNullArgument.type.toKotlinType(), context.irBuiltIns))
statements.add(constFalse)
}
}
} else if (isNullCheckOfConstant(expression, context)) {
if (expression.getValueArgument(0)!!.isNullConst() && expression.getValueArgument(1)!!.isNullConst()) {
IrConstImpl.constTrue(expression.startOffset, expression.endOffset, context.irBuiltIns.booleanType)
} else {
IrConstImpl.constFalse(expression.startOffset, expression.endOffset, context.irBuiltIns.booleanType)
}
} else {
expression
} }
getOperandsIfCallToEqeqOrEquals(expression)?.let { (left, right) ->
val constFalse =
IrConstImpl.constFalse(expression.startOffset, expression.endOffset, context.irBuiltIns.booleanType)
return when {
left.isNullConst() && right.isNullConst() ->
IrConstImpl.constTrue(expression.startOffset, expression.endOffset, context.irBuiltIns.booleanType)
left.isNullConst() && right is IrConst<*> || right.isNullConst() && left is IrConst<*> ->
constFalse
right.isNullConst() && left.type.unboxInlineClass().isPrimitiveType() ||
left.isNullConst() && right.type.unboxInlineClass().isPrimitiveType() -> {
val nonNullArgument = if (left.isNullConst()) right else left
if (hasNoSideEffectsForNullCompare(nonNullArgument)) {
constFalse
} else {
IrBlockImpl(expression.startOffset, expression.endOffset, expression.type, expression.origin).apply {
statements.add(
nonNullArgument.coerceToUnitIfNeeded(
nonNullArgument.type.toKotlinType(),
context.irBuiltIns
)
)
statements.add(constFalse)
}
}
}
else -> expression
}
}
return expression
} }
override fun visitWhen(expression: IrWhen): IrExpression { override fun visitWhen(expression: IrWhen): IrExpression {
@@ -103,10 +120,12 @@ class JvmBuiltinOptimizationLowering(val context: JvmBackendContext) : FileLower
it.condition.isFalseConst() && isCompilerGenerated it.condition.isFalseConst() && isCompilerGenerated
} }
if (expression.origin == IrStatementOrigin.ANDAND) { if (expression.origin == IrStatementOrigin.ANDAND) {
assert(expression.type.isBoolean() assert(
&& expression.branches.size == 2 expression.type.isBoolean()
&& expression.branches[1].condition.isTrueConst() && expression.branches.size == 2
&& expression.branches[1].result.isFalseConst()) { && expression.branches[1].condition.isTrueConst()
&& expression.branches[1].result.isFalseConst()
) {
"ANDAND condition should have an 'if true then false' body on its second branch. " + "ANDAND condition should have an 'if true then false' body on its second branch. " +
"Failing expression: ${expression.dump()}" "Failing expression: ${expression.dump()}"
} }
@@ -126,7 +145,8 @@ class JvmBuiltinOptimizationLowering(val context: JvmBackendContext) : FileLower
expression.type.isBoolean() expression.type.isBoolean()
&& expression.branches.size == 2 && expression.branches.size == 2
&& expression.branches[0].result.isTrueConst() && expression.branches[0].result.isTrueConst()
&& expression.branches[1].condition.isTrueConst()) { && expression.branches[1].condition.isTrueConst()
) {
"OROR condition should have an 'if a then true' body on its first branch, " + "OROR condition should have an 'if a then true' body on its first branch, " +
"and an 'if true then b' body on its second branch. " + "and an 'if true then b' body on its second branch. " +
"Failing expression: ${expression.dump()}" "Failing expression: ${expression.dump()}"
@@ -228,7 +248,8 @@ class JvmBuiltinOptimizationLowering(val context: JvmBackendContext) : FileLower
if (first is IrVariable if (first is IrVariable
&& first.origin == IrDeclarationOrigin.IR_TEMPORARY_VARIABLE && first.origin == IrDeclarationOrigin.IR_TEMPORARY_VARIABLE
&& second is IrGetValue && second is IrGetValue
&& first.symbol == second.symbol) { && first.symbol == second.symbol
) {
expression.statements.clear() expression.statements.clear()
first.initializer?.let { expression.statements.add(it) } first.initializer?.let { expression.statements.add(it) }
} }
@@ -0,0 +1,2 @@
fun box(): String =
if (1.equals(null)) "FAIL" else "OK"
@@ -0,0 +1,20 @@
fun f(): Boolean = "non-primitive" == null
fun g(): Boolean = null == "non-primitive"
fun h(): Boolean = "non-primitive".equals(null)
//fun i(): Boolean = null.equals("non-primitive")
//See KT-33757
// JVM does not optimize h() to constant false
// 0 IF
// JVM_TEMPLATES
// 2 ICONST_0
// 1 ACONST_NULL
// 1 INVOKEVIRTUAL
// JVM_IR_TEMPLATES
// 0 ACONST_NULL
// 0 INVOKESTATIC
// 0 INVOKEVIRTUAL
// 3 ICONST_0
@@ -17830,6 +17830,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/boxedLongEqualsLong.kt"); runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/boxedLongEqualsLong.kt");
} }
@TestMetadata("intEqualsNull.kt")
public void testIntEqualsNull() throws Exception {
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNull.kt");
}
@TestMetadata("intEqualsNullableInt.kt") @TestMetadata("intEqualsNullableInt.kt")
public void testIntEqualsNullableInt() throws Exception { public void testIntEqualsNullableInt() throws Exception {
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNullableInt.kt"); runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNullableInt.kt");
@@ -1034,6 +1034,11 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/conditions/nonZeroCompareInWhile.kt"); runTest("compiler/testData/codegen/bytecodeText/conditions/nonZeroCompareInWhile.kt");
} }
@TestMetadata("nullCompareConst.kt")
public void testNullCompareConst() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/conditions/nullCompareConst.kt");
}
@TestMetadata("nullCompareInDoWhile.kt") @TestMetadata("nullCompareInDoWhile.kt")
public void testNullCompareInDoWhile() throws Exception { public void testNullCompareInDoWhile() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/conditions/nullCompareInDoWhile.kt"); runTest("compiler/testData/codegen/bytecodeText/conditions/nullCompareInDoWhile.kt");
@@ -17830,6 +17830,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/boxedLongEqualsLong.kt"); runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/boxedLongEqualsLong.kt");
} }
@TestMetadata("intEqualsNull.kt")
public void testIntEqualsNull() throws Exception {
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNull.kt");
}
@TestMetadata("intEqualsNullableInt.kt") @TestMetadata("intEqualsNullableInt.kt")
public void testIntEqualsNullableInt() throws Exception { public void testIntEqualsNullableInt() throws Exception {
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNullableInt.kt"); runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNullableInt.kt");
@@ -16715,6 +16715,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/boxedLongEqualsLong.kt"); runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/boxedLongEqualsLong.kt");
} }
@TestMetadata("intEqualsNull.kt")
public void testIntEqualsNull() throws Exception {
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNull.kt");
}
@TestMetadata("intEqualsNullableInt.kt") @TestMetadata("intEqualsNullableInt.kt")
public void testIntEqualsNullableInt() throws Exception { public void testIntEqualsNullableInt() throws Exception {
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNullableInt.kt"); runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNullableInt.kt");
@@ -1034,6 +1034,11 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/conditions/nonZeroCompareInWhile.kt"); runTest("compiler/testData/codegen/bytecodeText/conditions/nonZeroCompareInWhile.kt");
} }
@TestMetadata("nullCompareConst.kt")
public void testNullCompareConst() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/conditions/nullCompareConst.kt");
}
@TestMetadata("nullCompareInDoWhile.kt") @TestMetadata("nullCompareInDoWhile.kt")
public void testNullCompareInDoWhile() throws Exception { public void testNullCompareInDoWhile() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/conditions/nullCompareInDoWhile.kt"); runTest("compiler/testData/codegen/bytecodeText/conditions/nullCompareInDoWhile.kt");
@@ -13775,6 +13775,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/boxedLongEqualsLong.kt"); runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/boxedLongEqualsLong.kt");
} }
@TestMetadata("intEqualsNull.kt")
public void testIntEqualsNull() throws Exception {
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNull.kt");
}
@TestMetadata("intEqualsNullableInt.kt") @TestMetadata("intEqualsNullableInt.kt")
public void testIntEqualsNullableInt() throws Exception { public void testIntEqualsNullableInt() throws Exception {
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNullableInt.kt"); runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNullableInt.kt");
@@ -14930,6 +14930,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/boxedLongEqualsLong.kt"); runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/boxedLongEqualsLong.kt");
} }
@TestMetadata("intEqualsNull.kt")
public void testIntEqualsNull() throws Exception {
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNull.kt");
}
@TestMetadata("intEqualsNullableInt.kt") @TestMetadata("intEqualsNullableInt.kt")
public void testIntEqualsNullableInt() throws Exception { public void testIntEqualsNullableInt() throws Exception {
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNullableInt.kt"); runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNullableInt.kt");