JVM_IR: Fix null-constant comparison with primitive types.
This commit is contained in:
committed by
max-kammerer
parent
e21da3a61a
commit
e7d0909979
+65
-44
@@ -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) }
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -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
|
||||||
+5
@@ -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");
|
||||||
|
|||||||
+5
@@ -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");
|
||||||
|
|||||||
+5
@@ -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");
|
||||||
|
|||||||
+5
@@ -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");
|
||||||
|
|||||||
Generated
+5
@@ -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");
|
||||||
|
|||||||
+5
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user