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");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("safeCallAndElvisChains.kt")
|
||||
public void testSafeCallAndElvisChains() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/safeCallAndElvisChains.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("trivialInstanceOf.kt")
|
||||
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) ||
|
||||
(!outer.ifNullExpr.isTrivial() && innerKeepsNull != false && innerDiscardsNonNull != false)
|
||||
) return this
|
||||
return inner.createIrBuilder().irBlock {
|
||||
val result = inner.createIrBuilder().irBlock {
|
||||
val ifNull = outer.substitute(inner.ifNullExpr, innerKeepsNull, inner.type)
|
||||
val ifNotNull = outer.substitute(inner.ifNotNullExpr, innerDiscardsNonNull, inner.type)
|
||||
+inner.subjectVar
|
||||
+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 =
|
||||
@@ -126,30 +129,32 @@ class IfNullExpressionsFusionLowering(val context: CommonBackendContext) : FileL
|
||||
private fun IfNullExpr.createIrBuilder() =
|
||||
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) {
|
||||
is IrConst<*> ->
|
||||
return value == null
|
||||
is IrGetValue -> {
|
||||
if (symbol == knownVariableSymbol) return knownVariableIsNull
|
||||
if (!type.isNullable()) return false
|
||||
is IrConst<*> -> value == null
|
||||
is IrGetValue -> when {
|
||||
symbol == knownVariableSymbol -> knownVariableIsNull
|
||||
!type.isNullable() -> false
|
||||
else -> null
|
||||
}
|
||||
is IrConstructorCall,
|
||||
is IrGetSingletonValue,
|
||||
is IrFunctionExpression,
|
||||
is IrCallableReference<*>,
|
||||
is IrClassReference,
|
||||
is IrGetClass ->
|
||||
return false
|
||||
is IrGetClass -> false
|
||||
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"
|
||||
private fun IrCall.isStableCall() =
|
||||
symbol.owner.fileOrNull == currentFile
|
||||
private fun IrDeclaration.isStable() =
|
||||
fileOrNull == currentFile
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("safeCallAndElvisChains.kt")
|
||||
public void testSafeCallAndElvisChains() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/safeCallAndElvisChains.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("trivialInstanceOf.kt")
|
||||
public void testTrivialInstanceOf() throws Exception {
|
||||
|
||||
+6
@@ -4616,6 +4616,12 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
|
||||
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
|
||||
@TestMetadata("trivialInstanceOf.kt")
|
||||
public void testTrivialInstanceOf() throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user