From a4918748a52cc92be7f9ede943af407abb642861 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Wed, 4 Oct 2017 17:46:46 +0300 Subject: [PATCH] Specialize not-null values using information from current analysis pass Instead of a separate analysis pass to determine variable types at the point of null checks, use current data flow information and transform possibly nullable values to definitely non-null values using a special intrinsic. This allows to perform a single data flow analysis pass per RNCE transformation pass (instead of two passes). --- .../nullCheck/NullabilityInterpreter.kt | 4 + .../RedundantNullCheckMethodTransformer.kt | 108 +++++------------- .../kotlin/codegen/pseudoInsns/PseudoInsns.kt | 7 +- 3 files changed, 39 insertions(+), 80 deletions(-) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/NullabilityInterpreter.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/NullabilityInterpreter.kt index b7b6d482120..1213a3a1892 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/NullabilityInterpreter.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/NullabilityInterpreter.kt @@ -19,6 +19,8 @@ package org.jetbrains.kotlin.codegen.optimization.nullCheck import org.jetbrains.kotlin.codegen.optimization.boxing.* import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue +import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsn +import org.jetbrains.kotlin.codegen.pseudoInsns.isPseudo import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode @@ -72,6 +74,8 @@ class NullabilityInterpreter : OptimizationBasicInterpreter() { ProgressionIteratorBasicValue.byProgressionClassType(values[0].type) insn.isNextMethodCallOfProgressionIterator(values) -> NotNullBasicValue(resultType) + insn.isPseudo(PseudoInsn.AS_NOT_NULL) -> + NotNullBasicValue(values[0].type) else -> defaultResult } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt index 9460548a0ad..664384f9271 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt @@ -19,8 +19,6 @@ package org.jetbrains.kotlin.codegen.optimization.nullCheck import org.jetbrains.kotlin.codegen.coroutines.withInstructionAdapter import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods -import org.jetbrains.kotlin.codegen.optimization.DeadCodeEliminationMethodTransformer -import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue import org.jetbrains.kotlin.codegen.optimization.common.debugText import org.jetbrains.kotlin.codegen.optimization.common.isInsn @@ -28,12 +26,12 @@ import org.jetbrains.kotlin.codegen.optimization.fixStack.peek import org.jetbrains.kotlin.codegen.optimization.fixStack.top import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer import org.jetbrains.kotlin.codegen.pseudoInsns.PseudoInsn +import org.jetbrains.kotlin.codegen.pseudoInsns.asNotNull import org.jetbrains.kotlin.codegen.pseudoInsns.isPseudo import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.utils.SmartList import org.jetbrains.org.objectweb.asm.Label import org.jetbrains.org.objectweb.asm.Opcodes -import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter import org.jetbrains.org.objectweb.asm.tree.* @@ -48,58 +46,16 @@ class RedundantNullCheckMethodTransformer : MethodTransformer() { fun run(): Boolean { if (methodNode.instructions.toArray().none { it.isOptimizable() }) return false - val checkedReferenceTypes = analyzeTypesAndRemoveDeadCode() - eliminateRedundantChecks(checkedReferenceTypes) - - return changes - } - - private fun analyzeTypesAndRemoveDeadCode(): Map { - val insns = methodNode.instructions.toArray() - val frames = analyze(internalClassName, methodNode, OptimizationBasicInterpreter()) - - val relevantReferenceTypes = HashMap() - insnLoop@ for (i in insns.indices) { - val insn = insns[i] - val frame = frames[i] ?: continue - when { - insn.isInstanceOfOrNullCheck() -> - relevantReferenceTypes[insn] = frame.top()?.type ?: continue@insnLoop - insn.isCheckParameterIsNotNull() || - insn.isCheckExpressionValueIsNotNull() -> - relevantReferenceTypes[insn] = frame.peek(1)?.type ?: continue@insnLoop - insn.isPseudo(PseudoInsn.STORE_NOT_NULL) -> { - val previous = insn.previous ?: continue@insnLoop - if (previous.opcode != Opcodes.ASTORE) continue@insnLoop - val previousFrame = frames[i - 1] ?: continue@insnLoop - relevantReferenceTypes[insn] = previousFrame.top()?.type ?: continue@insnLoop - } - } - } - - val dceResult = DeadCodeEliminationMethodTransformer().removeDeadCodeByFrames(methodNode, frames) - if (dceResult.hasRemovedAnything()) { - changes = true - } - - return relevantReferenceTypes - } - - private fun eliminateRedundantChecks( - checkedReferenceTypes: Map - ) { - val nullabilityAssumptions = injectNullabilityAssumptions(checkedReferenceTypes) + val nullabilityAssumptions = NullabilityAssumptionsBuilder().injectNullabilityAssumptions() val nullabilityMap = analyzeNullabilities() nullabilityAssumptions.revert() transformTrivialChecks(nullabilityMap) - } - private fun injectNullabilityAssumptions( - checkedReferenceTypes: Map - ) = NullabilityAssumptionsBuilder(checkedReferenceTypes).injectNullabilityAssumptions() + return changes + } private fun analyzeNullabilities(): Map { val frames = analyze(internalClassName, methodNode, NullabilityInterpreter()) @@ -188,9 +144,7 @@ class RedundantNullCheckMethodTransformer : MethodTransformer() { } } - private inner class NullabilityAssumptionsBuilder( - val relatedReferenceTypes: Map - ) { + private inner class NullabilityAssumptionsBuilder { private val checksDependingOnVariable = HashMap>() @@ -254,9 +208,7 @@ class RedundantNullCheckMethodTransformer : MethodTransformer() { val nullabilityAssumptions = NullabilityAssumptions() for ((varIndex, dependentChecks) in checksDependingOnVariable) { for (checkInsn in dependentChecks) { - val varType = relatedReferenceTypes[checkInsn] - ?: AsmTypes.OBJECT_TYPE - nullabilityAssumptions.injectAssumptionsForInsn(varIndex, checkInsn, varType) + nullabilityAssumptions.injectAssumptionsForInsn(varIndex, checkInsn) } } for (insn in methodNode.instructions) { @@ -267,28 +219,28 @@ class RedundantNullCheckMethodTransformer : MethodTransformer() { return nullabilityAssumptions } - private fun NullabilityAssumptions.injectAssumptionsForInsn(varIndex: Int, insn: AbstractInsnNode, varType: Type) { + private fun NullabilityAssumptions.injectAssumptionsForInsn(varIndex: Int, insn: AbstractInsnNode) { when (insn.opcode) { Opcodes.IFNULL, Opcodes.IFNONNULL -> - injectAssumptionsForNullCheck(varIndex, insn as JumpInsnNode, varType) + injectAssumptionsForNullCheck(varIndex, insn as JumpInsnNode) Opcodes.INVOKESTATIC -> { when { insn.isCheckParameterIsNotNull() || insn.isCheckExpressionValueIsNotNull() -> - injectAssumptionsForNotNullAssertion(varIndex, insn, varType) + injectAssumptionsForNotNullAssertion(varIndex, insn) insn.isPseudo(PseudoInsn.STORE_NOT_NULL) -> - injectCodeForStoreNotNull(insn, varType) + injectCodeForStoreNotNull(insn) else -> throw AssertionError("Expected non-null assertion: ${insn.debugText}") } } Opcodes.INSTANCEOF -> - injectAssumptionsForInstanceOfCheck(varIndex, insn, varType) + injectAssumptionsForInstanceOfCheck(varIndex, insn) } } - private fun NullabilityAssumptions.injectAssumptionsForNullCheck(varIndex: Int, insn: JumpInsnNode, varType: Type) { + private fun NullabilityAssumptions.injectAssumptionsForNullCheck(varIndex: Int, insn: JumpInsnNode) { // ALOAD v // IFNULL L // <...> -- v is not null here @@ -308,15 +260,16 @@ class RedundantNullCheckMethodTransformer : MethodTransformer() { insert(insertAfterNull, listOfSynthetics { aconst(null) - store(varIndex, varType) + store(varIndex, AsmTypes.OBJECT_TYPE) if (jumpsIfNull) { goTo(originalLabel.label) } }) insert(insertAfterNonNull, listOfSynthetics { - anew(varType) - store(varIndex, varType) + load(varIndex, AsmTypes.OBJECT_TYPE) + asNotNull() + store(varIndex, AsmTypes.OBJECT_TYPE) if (!jumpsIfNull) { goTo(originalLabel.label) } @@ -324,7 +277,7 @@ class RedundantNullCheckMethodTransformer : MethodTransformer() { } } - private fun NullabilityAssumptions.injectAssumptionsForNotNullAssertion(varIndex: Int, insn: AbstractInsnNode, varType: Type) { + private fun NullabilityAssumptions.injectAssumptionsForNotNullAssertion(varIndex: Int, insn: AbstractInsnNode) { // ALOAD v // LDC * // INVOKESTATIC checkParameterIsNotNull @@ -337,12 +290,13 @@ class RedundantNullCheckMethodTransformer : MethodTransformer() { // <...> -- v is not null here (otherwise an exception was thrown) methodNode.instructions.insert(insn, listOfSynthetics { - anew(varType) - store(varIndex, varType) + load(varIndex, AsmTypes.OBJECT_TYPE) + asNotNull() + store(varIndex, AsmTypes.OBJECT_TYPE) }) } - private fun NullabilityAssumptions.injectAssumptionsForInstanceOfCheck(varIndex: Int, insn: AbstractInsnNode, varType: Type) { + private fun NullabilityAssumptions.injectAssumptionsForInstanceOfCheck(varIndex: Int, insn: AbstractInsnNode) { // ALOAD v // INSTANCEOF T // IFEQ L @@ -372,8 +326,9 @@ class RedundantNullCheckMethodTransformer : MethodTransformer() { methodNode.instructions.run { insert(insertAfterNotNull, listOfSynthetics { - anew(varType) - store(varIndex, varType) + load(varIndex, AsmTypes.OBJECT_TYPE) + asNotNull() + store(varIndex, AsmTypes.OBJECT_TYPE) if (originalLabel != null) { goTo(originalLabel.label) } @@ -392,7 +347,7 @@ class RedundantNullCheckMethodTransformer : MethodTransformer() { methodNode.maxStack = methodNode.maxStack + 1 //will be recalculated in prepareForEmitting } - private fun NullabilityAssumptions.injectCodeForStoreNotNull(insn: AbstractInsnNode, varType: Type) { + private fun NullabilityAssumptions.injectCodeForStoreNotNull(insn: AbstractInsnNode) { // ASTORE v // [STORE_NOT_NULL] // <...> -- v is not null here because codegen told us so @@ -401,8 +356,10 @@ class RedundantNullCheckMethodTransformer : MethodTransformer() { methodNode.instructions.run { insert(insn, listOfSynthetics { - anew(varType) - store((previous as VarInsnNode).`var`, varType) + val varIndex = (previous as VarInsnNode).`var` + load(varIndex, AsmTypes.OBJECT_TYPE) + asNotNull() + store(varIndex, AsmTypes.OBJECT_TYPE) }) } } @@ -462,13 +419,6 @@ internal fun AbstractInsnNode.isThrowIntrinsic() = name in THROW_INTRINSIC_METHOD_NAMES } -internal fun AbstractInsnNode.isThrowIntrinsicWithoutArguments() = - isInsn(Opcodes.INVOKESTATIC) { - owner == IntrinsicMethods.INTRINSICS_CLASS_NAME && - name in THROW_INTRINSIC_METHOD_NAMES && - desc == "()V" - } - internal val THROW_INTRINSIC_METHOD_NAMES = setOf( "throwNpe", diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/pseudoInsns/PseudoInsns.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/pseudoInsns/PseudoInsns.kt index 22721a318fd..894f2d2b5ef 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/pseudoInsns/PseudoInsns.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/pseudoInsns/PseudoInsns.kt @@ -31,7 +31,8 @@ enum class PseudoInsn(val signature: String = "()V") { FAKE_ALWAYS_FALSE_IFEQ("()I"), SAVE_STACK_BEFORE_TRY, RESTORE_STACK_IN_TRY_CATCH, - STORE_NOT_NULL + STORE_NOT_NULL, + AS_NOT_NULL("(Ljava/lang/Object;)Ljava/lang/Object;") ; fun emit(iv: InstructionAdapter) { @@ -72,5 +73,9 @@ fun InstructionAdapter.storeNotNull() { PseudoInsn.STORE_NOT_NULL.emit(this) } +fun InstructionAdapter.asNotNull() { + PseudoInsn.AS_NOT_NULL.emit(this) +} + fun AbstractInsnNode.isPseudo(pseudoInsn: PseudoInsn) = pseudoInsn.isa(this) \ No newline at end of file