JVM_IR: optimize more if-null chains
1. consider reads of fields from the same file "stable" just like
functions, i.e. assume their nullability information is correct
2. apply if-null fusion repeatedly until the subject is no longer a
nested if-null expression
This commit is contained in:
+6
@@ -4616,6 +4616,12 @@ public class FirBytecodeTextTestGenerated extends AbstractFirBytecodeTextTest {
|
|||||||
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/reifiedNullIs.kt");
|
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/reifiedNullIs.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("safeCallAndElvisChains.kt")
|
||||||
|
public void testSafeCallAndElvisChains() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/safeCallAndElvisChains.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("trivialInstanceOf.kt")
|
@TestMetadata("trivialInstanceOf.kt")
|
||||||
public void testTrivialInstanceOf() throws Exception {
|
public void testTrivialInstanceOf() throws Exception {
|
||||||
|
|||||||
+20
-15
@@ -103,12 +103,15 @@ class IfNullExpressionsFusionLowering(val context: CommonBackendContext) : FileL
|
|||||||
if ((!outer.ifNotNullExpr.isTrivial() && innerKeepsNull != true && innerDiscardsNonNull != true) ||
|
if ((!outer.ifNotNullExpr.isTrivial() && innerKeepsNull != true && innerDiscardsNonNull != true) ||
|
||||||
(!outer.ifNullExpr.isTrivial() && innerKeepsNull != false && innerDiscardsNonNull != false)
|
(!outer.ifNullExpr.isTrivial() && innerKeepsNull != false && innerDiscardsNonNull != false)
|
||||||
) return this
|
) return this
|
||||||
return inner.createIrBuilder().irBlock {
|
val result = inner.createIrBuilder().irBlock {
|
||||||
val ifNull = outer.substitute(inner.ifNullExpr, innerKeepsNull, inner.type)
|
val ifNull = outer.substitute(inner.ifNullExpr, innerKeepsNull, inner.type)
|
||||||
val ifNotNull = outer.substitute(inner.ifNotNullExpr, innerDiscardsNonNull, inner.type)
|
val ifNotNull = outer.substitute(inner.ifNotNullExpr, innerDiscardsNonNull, inner.type)
|
||||||
+inner.subjectVar
|
+inner.subjectVar
|
||||||
+irIfNull(outer.type, irGet(inner.subjectVar), ifNull, ifNotNull)
|
+irIfNull(outer.type, irGet(inner.subjectVar), ifNull, ifNotNull)
|
||||||
}
|
} as IrBlock
|
||||||
|
// Each `FUSE_IF_NULL` removes one level of `IfNull` from the expression being checked,
|
||||||
|
// so this eventually terminates when we no longer have `IfNull(IfNull(...), ...)`.
|
||||||
|
return result.fuseIfNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun IfNullExpr.substitute(subject: IrExpression, knownNullability: Boolean?, temporaryVarType: IrType): IrExpression =
|
private fun IfNullExpr.substitute(subject: IrExpression, knownNullability: Boolean?, temporaryVarType: IrType): IrExpression =
|
||||||
@@ -126,30 +129,32 @@ class IfNullExpressionsFusionLowering(val context: CommonBackendContext) : FileL
|
|||||||
private fun IfNullExpr.createIrBuilder() =
|
private fun IfNullExpr.createIrBuilder() =
|
||||||
context.createIrBuilder((subjectVar.parent as IrSymbolOwner).symbol, subjectVar.startOffset, subjectVar.endOffset)
|
context.createIrBuilder((subjectVar.parent as IrSymbolOwner).symbol, subjectVar.startOffset, subjectVar.endOffset)
|
||||||
|
|
||||||
private fun IrExpression.isNull(knownVariableSymbol: IrVariableSymbol, knownVariableIsNull: Boolean): Boolean? {
|
private fun IrExpression.isNull(knownVariableSymbol: IrVariableSymbol, knownVariableIsNull: Boolean): Boolean? =
|
||||||
when (this) {
|
when (this) {
|
||||||
is IrConst<*> ->
|
is IrConst<*> -> value == null
|
||||||
return value == null
|
is IrGetValue -> when {
|
||||||
is IrGetValue -> {
|
symbol == knownVariableSymbol -> knownVariableIsNull
|
||||||
if (symbol == knownVariableSymbol) return knownVariableIsNull
|
!type.isNullable() -> false
|
||||||
if (!type.isNullable()) return false
|
else -> null
|
||||||
}
|
}
|
||||||
is IrConstructorCall,
|
is IrConstructorCall,
|
||||||
is IrGetSingletonValue,
|
is IrGetSingletonValue,
|
||||||
is IrFunctionExpression,
|
is IrFunctionExpression,
|
||||||
is IrCallableReference<*>,
|
is IrCallableReference<*>,
|
||||||
is IrClassReference,
|
is IrClassReference,
|
||||||
is IrGetClass ->
|
is IrGetClass -> false
|
||||||
return false
|
|
||||||
is IrCall ->
|
is IrCall ->
|
||||||
if (!type.isNullable() && isStableCall()) return false
|
if (!type.isNullable() && symbol.owner.isStable()) false else null
|
||||||
|
is IrGetField ->
|
||||||
|
if (!type.isNullable() && symbol.owner.isStable()) false else null
|
||||||
|
is IrBlock ->
|
||||||
|
(statements.singleOrNull() as IrExpression?)?.takeIf { it.type == type }?.isNull(knownVariableSymbol, knownVariableIsNull)
|
||||||
|
else -> null
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO make calls to the declarations within the same module "stable"
|
// TODO make calls to the declarations within the same module "stable"
|
||||||
private fun IrCall.isStableCall() =
|
private fun IrDeclaration.isStable() =
|
||||||
symbol.owner.fileOrNull == currentFile
|
fileOrNull == currentFile
|
||||||
}
|
}
|
||||||
|
|
||||||
private class IfNullExpr(
|
private class IfNullExpr(
|
||||||
|
|||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
class A(val x: String) {
|
||||||
|
fun y() = x
|
||||||
|
// Both `x` and `y()` assumed to respect their nullability information,
|
||||||
|
// so only `a` and `b` need to be checked.
|
||||||
|
fun foo1(a: A?, b: A?) = a?.x ?: b?.x ?: x // if (a == null) if (b == null) x else b.x else a.x
|
||||||
|
fun foo2(a: A?, b: A?) = a?.y() ?: b?.y() ?: y() // if (a == null) if (b == null) y() else b.y() else a.y()
|
||||||
|
}
|
||||||
|
|
||||||
|
// JVM_TEMPLATES
|
||||||
|
// Optimization not implemented
|
||||||
|
// 8 IFNULL
|
||||||
|
// 0 IFNONNULL
|
||||||
|
// 2 ACONST_NULL
|
||||||
|
|
||||||
|
// JVM_IR_TEMPLATES
|
||||||
|
// 0 IFNULL
|
||||||
|
// 4 IFNONNULL
|
||||||
|
// 0 ACONST_NULL
|
||||||
+6
@@ -4484,6 +4484,12 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
|||||||
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/reifiedNullIs.kt");
|
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/reifiedNullIs.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("safeCallAndElvisChains.kt")
|
||||||
|
public void testSafeCallAndElvisChains() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/safeCallAndElvisChains.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("trivialInstanceOf.kt")
|
@TestMetadata("trivialInstanceOf.kt")
|
||||||
public void testTrivialInstanceOf() throws Exception {
|
public void testTrivialInstanceOf() throws Exception {
|
||||||
|
|||||||
+6
@@ -4616,6 +4616,12 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
|
|||||||
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/reifiedNullIs.kt");
|
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/reifiedNullIs.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("safeCallAndElvisChains.kt")
|
||||||
|
public void testSafeCallAndElvisChains() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/safeCallAndElvisChains.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("trivialInstanceOf.kt")
|
@TestMetadata("trivialInstanceOf.kt")
|
||||||
public void testTrivialInstanceOf() throws Exception {
|
public void testTrivialInstanceOf() throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user