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.declarations.IrDeclarationOrigin
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.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
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.toKotlinType
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)
}
private fun isNullCheckOfPrimitiveTypeValue(call: IrCall, context: JvmBackendContext): Boolean {
if (call.symbol == context.irBuiltIns.eqeqSymbol) {
val left = call.getValueArgument(0)!!
val right = call.getValueArgument(1)!!
// When used for null checks, it is safe to eliminate constants and local variable loads.
// Even if a local variable of simple type is updated via the debugger it still cannot
// be null.
return (right.isNullConst() && left.type.unboxInlineClass().isPrimitiveType())
|| (left.isNullConst() && right.type.unboxInlineClass().isPrimitiveType())
}
return false
}
private val IrFunction.isObjectEquals
get() = name.asString() == "equals" &&
valueParameters.count() == 1 &&
valueParameters[0].type.isNullableAny() &&
extensionReceiverParameter == null &&
dispatchReceiverParameter != null
private fun isNullCheckOfConstant(call: IrCall, context: JvmBackendContext): Boolean {
private fun getOperandsIfCallToEqeqOrEquals(call: IrCall): Pair<IrExpression, IrExpression>? {
if (call.symbol == context.irBuiltIns.eqeqSymbol) {
val left = call.getValueArgument(0)!!
val right = call.getValueArgument(1)!!
return (right.isNullConst() && left is IrConst<*>)
|| (left.isNullConst() && right is IrConst<*>)
return left to right
} 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) {
irFile.transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitCall(expression: IrCall): IrExpression {
expression.transformChildrenVoid(this)
return if (isNegation(expression, context) && isNegation(expression.dispatchReceiver!!, context)) {
(expression.dispatchReceiver as IrCall).dispatchReceiver!!
} else if (isNullCheckOfPrimitiveTypeValue(expression, context)) {
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
if (isNegation(expression, context) && isNegation(expression.dispatchReceiver!!, context)) {
return (expression.dispatchReceiver as IrCall).dispatchReceiver!!
}
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 {
@@ -103,10 +120,12 @@ class JvmBuiltinOptimizationLowering(val context: JvmBackendContext) : FileLower
it.condition.isFalseConst() && isCompilerGenerated
}
if (expression.origin == IrStatementOrigin.ANDAND) {
assert(expression.type.isBoolean()
&& expression.branches.size == 2
&& expression.branches[1].condition.isTrueConst()
&& expression.branches[1].result.isFalseConst()) {
assert(
expression.type.isBoolean()
&& expression.branches.size == 2
&& expression.branches[1].condition.isTrueConst()
&& expression.branches[1].result.isFalseConst()
) {
"ANDAND condition should have an 'if true then false' body on its second branch. " +
"Failing expression: ${expression.dump()}"
}
@@ -126,7 +145,8 @@ class JvmBuiltinOptimizationLowering(val context: JvmBackendContext) : FileLower
expression.type.isBoolean()
&& expression.branches.size == 2
&& 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, " +
"and an 'if true then b' body on its second branch. " +
"Failing expression: ${expression.dump()}"
@@ -228,7 +248,8 @@ class JvmBuiltinOptimizationLowering(val context: JvmBackendContext) : FileLower
if (first is IrVariable
&& first.origin == IrDeclarationOrigin.IR_TEMPORARY_VARIABLE
&& second is IrGetValue
&& first.symbol == second.symbol) {
&& first.symbol == second.symbol
) {
expression.statements.clear()
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");
}
@TestMetadata("intEqualsNull.kt")
public void testIntEqualsNull() throws Exception {
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNull.kt");
}
@TestMetadata("intEqualsNullableInt.kt")
public void testIntEqualsNullableInt() throws Exception {
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");
}
@TestMetadata("nullCompareConst.kt")
public void testNullCompareConst() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/conditions/nullCompareConst.kt");
}
@TestMetadata("nullCompareInDoWhile.kt")
public void testNullCompareInDoWhile() throws Exception {
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");
}
@TestMetadata("intEqualsNull.kt")
public void testIntEqualsNull() throws Exception {
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNull.kt");
}
@TestMetadata("intEqualsNullableInt.kt")
public void testIntEqualsNullableInt() throws Exception {
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");
}
@TestMetadata("intEqualsNull.kt")
public void testIntEqualsNull() throws Exception {
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNull.kt");
}
@TestMetadata("intEqualsNullableInt.kt")
public void testIntEqualsNullableInt() throws Exception {
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");
}
@TestMetadata("nullCompareConst.kt")
public void testNullCompareConst() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/conditions/nullCompareConst.kt");
}
@TestMetadata("nullCompareInDoWhile.kt")
public void testNullCompareInDoWhile() throws Exception {
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");
}
@TestMetadata("intEqualsNull.kt")
public void testIntEqualsNull() throws Exception {
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNull.kt");
}
@TestMetadata("intEqualsNullableInt.kt")
public void testIntEqualsNullableInt() throws Exception {
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");
}
@TestMetadata("intEqualsNull.kt")
public void testIntEqualsNull() throws Exception {
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNull.kt");
}
@TestMetadata("intEqualsNullableInt.kt")
public void testIntEqualsNullableInt() throws Exception {
runTest("compiler/testData/codegen/box/primitiveTypes/equalityWithObject/intEqualsNullableInt.kt");