diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformationClassBuilder.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformationClassBuilder.kt index c05174d2d3a..e657d05996a 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformationClassBuilder.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformationClassBuilder.kt @@ -421,7 +421,7 @@ private fun InstructionAdapter.generateResumeWithExceptionCheck() { private fun Type.fieldNameForVar(index: Int) = descriptor.first() + "$" + index -private fun withInstructionAdapter(block: InstructionAdapter.() -> Unit): InsnList { +inline fun withInstructionAdapter(block: InstructionAdapter.() -> Unit): InsnList { val tmpMethodNode = MethodNode() InstructionAdapter(tmpMethodNode).apply(block) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/ReifiedTypeInliner.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/ReifiedTypeInliner.kt index 7153bf15875..eeb1a1f5e47 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/ReifiedTypeInliner.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/ReifiedTypeInliner.kt @@ -70,7 +70,7 @@ class ReifiedTypeInliner(private val parametersMapping: TypeParameterMappings?) const val REIFIED_OPERATION_MARKER_METHOD_NAME = "reifiedOperationMarker" const val NEED_CLASS_REIFICATION_MARKER_METHOD_NAME = "needClassReification" - private fun isOperationReifiedMarker(insn: AbstractInsnNode) = + fun isOperationReifiedMarker(insn: AbstractInsnNode) = isReifiedMarker(insn) { it == REIFIED_OPERATION_MARKER_METHOD_NAME } private fun isReifiedMarker(insn: AbstractInsnNode, namePredicate: (String) -> Boolean): Boolean { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/DeadCodeEliminationMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/DeadCodeEliminationMethodTransformer.kt index f11e5a10f08..2d6337a09c2 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/DeadCodeEliminationMethodTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/DeadCodeEliminationMethodTransformer.kt @@ -29,9 +29,13 @@ class DeadCodeEliminationMethodTransformer : MethodTransformer() { } fun transformWithResult(internalClassName: String, methodNode: MethodNode): Result { + val frames = analyze(internalClassName, methodNode, OptimizationBasicInterpreter()) + return removeDeadCodeByFrames(methodNode, frames) + } + + fun removeDeadCodeByFrames(methodNode: MethodNode, frames: Array): Result { val removedNodes = HashSet() - val frames = analyze(internalClassName, methodNode, OptimizationBasicInterpreter()) val insnList = methodNode.instructions val insnsArray = insnList.toArray() @@ -51,6 +55,7 @@ class DeadCodeEliminationMethodTransformer : MethodTransformer() { } class Result(val removedNodes: Set) { + fun hasRemovedAnything() = removedNodes.isNotEmpty() fun isRemoved(node: AbstractInsnNode) = removedNodes.contains(node) fun isAlive(node: AbstractInsnNode) = !isRemoved(node) } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.java index 1e901b4c5ea..1c49d7c109d 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.java @@ -23,7 +23,7 @@ import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantBoxingMethodTra import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantCoercionToUnitTransformer; import org.jetbrains.kotlin.codegen.optimization.captured.CapturedVarsOptimizationMethodTransformer; import org.jetbrains.kotlin.codegen.optimization.common.UtilKt; -import org.jetbrains.kotlin.codegen.optimization.nullCheck.RedundantNullCheckMethodTransformer; +import org.jetbrains.kotlin.codegen.optimization.nullCheck.RedundantNullCheckV2MethodTransformer; import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer; import org.jetbrains.org.objectweb.asm.MethodVisitor; import org.jetbrains.org.objectweb.asm.tree.MethodNode; @@ -35,7 +35,7 @@ public class OptimizationMethodVisitor extends TransformationMethodVisitor { private static final MethodTransformer[] OPTIMIZATION_TRANSFORMERS = new MethodTransformer[] { new CapturedVarsOptimizationMethodTransformer(), - new RedundantNullCheckMethodTransformer(), + new RedundantNullCheckV2MethodTransformer(), new RedundantBoxingMethodTransformer(), new RedundantCoercionToUnitTransformer(), new DeadCodeEliminationMethodTransformer(), diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/StackTransformationUtils.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/StackTransformationUtils.kt index 0e5d35a8683..49c4f39b58b 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/StackTransformationUtils.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/fixStack/StackTransformationUtils.kt @@ -26,7 +26,7 @@ fun Frame.top(): V? = peek(0) fun Frame.peek(offset: Int): V? = - if (stackSize >= offset) getStack(stackSize - offset - 1) else null + if (stackSize > offset) getStack(stackSize - offset - 1) else null class SavedStackDescriptor( val savedValues: List, 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 0486c4d0030..b7b6d482120 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 @@ -25,7 +25,7 @@ import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode import org.jetbrains.org.objectweb.asm.tree.TypeInsnNode import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue -class NullabilityV2Interpreter : OptimizationBasicInterpreter() { +class NullabilityInterpreter : OptimizationBasicInterpreter() { override fun newOperation(insn: AbstractInsnNode): BasicValue? { val defaultResult = super.newOperation(insn) val resultType = defaultResult?.type 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 7a3802631ee..cf742f74150 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 @@ -37,18 +37,6 @@ class RedundantNullCheckMethodTransformer : MethodTransformer() { } } - private enum class Nullability { - NULL, NOT_NULL, NULLABLE - } - - private fun BasicValue.getNullability(): Nullability = - when (this) { - is NullBasicValue -> Nullability.NULL - is NotNullBasicValue -> Nullability.NOT_NULL - is ProgressionIteratorBasicValue -> Nullability.NOT_NULL - else -> Nullability.NULLABLE - } - private fun isAlwaysFalse(opcode: Int, nullability: Nullability) = (opcode == Opcodes.IFNULL && nullability == Nullability.NOT_NULL) || (opcode == Opcodes.IFNONNULL && nullability == Nullability.NULL) @@ -70,7 +58,7 @@ class RedundantNullCheckMethodTransformer : MethodTransformer() { } if (nullCheckIfs.isEmpty()) return false - val frames = analyze(internalClassName, methodNode, NullabilityV2Interpreter()) + val frames = analyze(internalClassName, methodNode, NullabilityInterpreter()) val redundantNullCheckIfs = nullCheckIfs.mapNotNull { insn -> frames[instructions.indexOf(insn)]?.top()?.let { top -> diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckV2MethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckV2MethodTransformer.kt new file mode 100644 index 00000000000..326c37431ff --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckV2MethodTransformer.kt @@ -0,0 +1,367 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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.optimization.DeadCodeEliminationMethodTransformer +import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter +import org.jetbrains.kotlin.codegen.optimization.common.isInsn +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.utils.SmartList +import org.jetbrains.kotlin.utils.addToStdlib.assertedCast +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.* + +class RedundantNullCheckV2MethodTransformer : MethodTransformer() { + override fun transform(internalClassName: String, methodNode: MethodNode) { + while (TransformerPass(internalClassName, methodNode).run()) {} + } + + private class TransformerPass(val internalClassName: String, val methodNode: MethodNode) { + private var changes = false + + private fun AbstractInsnNode.getIndex() = + methodNode.instructions.indexOf(this) + + fun run(): Boolean { + val checkedReferenceTypes = analyzeTypesAndRemoveDeadCode() + eliminateRedundantChecks(checkedReferenceTypes) + + return changes + } + + private fun analyzeTypesAndRemoveDeadCode(): Map { + val insns = methodNode.instructions.toArray() + val frames = analyze(internalClassName, methodNode, OptimizationBasicInterpreter()) + + val checkedReferenceTypes = HashMap() + for (i in insns.indices) { + val insn = insns[i] + val frame = frames[i] + if (insn.isInstanceOfOrNullCheck()) { + checkedReferenceTypes[insn] = frame?.top()?.type ?: continue + } + else if (insn.isCheckParameterNotNull()) { + checkedReferenceTypes[insn] = frame?.peek(1)?.type ?: continue + } + } + + val dceResult = DeadCodeEliminationMethodTransformer().removeDeadCodeByFrames(methodNode, frames) + if (dceResult.hasRemovedAnything()) { + changes = true + } + + return checkedReferenceTypes + } + + private fun eliminateRedundantChecks(checkedReferenceTypes: Map) { + val nullabilityAssumptions = injectNullabilityAssumptions(checkedReferenceTypes) + + val nullabilityMap = analyzeNullabilities() + + nullabilityAssumptions.revert() + + transformTrivialChecks(nullabilityMap) + } + + private fun injectNullabilityAssumptions(checkedReferenceTypes: Map) = + NullabilityAssumptionsBuilder(checkedReferenceTypes).injectNullabilityAssumptions() + + private fun analyzeNullabilities(): Map { + val frames = analyze(internalClassName, methodNode, NullabilityInterpreter()) + val insns = methodNode.instructions.toArray() + val nullabilityMap = HashMap() + for (i in insns.indices) { + val nullability = frames[i]?.top()?.getNullability() ?: continue + if (nullability == Nullability.NULLABLE) continue + + val insn = insns[i] + if (insn.isInstanceOfOrNullCheck()) { + nullabilityMap[insn] = nullability + } + } + return nullabilityMap + } + + private fun transformTrivialChecks(nullabilityMap: Map) { + for ((insn, nullability) in nullabilityMap) { + when (insn.opcode) { + Opcodes.IFNULL -> transformTrivialNullJump(insn as JumpInsnNode, nullability == Nullability.NULL) + Opcodes.IFNONNULL -> transformTrivialNullJump(insn as JumpInsnNode, nullability == Nullability.NOT_NULL) + Opcodes.INSTANCEOF -> transformInstanceOf(insn, nullability) + } + } + } + + private fun transformTrivialNullJump(insn: JumpInsnNode, alwaysTrue: Boolean) { + changes = true + + methodNode.instructions.run { + popReferenceValueBefore(insn) + if (alwaysTrue) { + set(insn, JumpInsnNode(Opcodes.GOTO, insn.label)) + } + else { + remove(insn) + } + } + } + + private fun transformInstanceOf(insn: AbstractInsnNode, nullability: Nullability) { + if (nullability != Nullability.NULL) return + if (ReifiedTypeInliner.isOperationReifiedMarker(insn.previous)) return + + changes = true + + val nextOpcode = insn.next?.opcode + if (nextOpcode == Opcodes.IFEQ || nextOpcode == Opcodes.IFNE) + transformNullInstanceOfWithJump(insn) + else + transformNullInstanceOf(insn) + } + + private fun transformNullInstanceOf(insn: AbstractInsnNode) { + methodNode.instructions.run { + popReferenceValueBefore(insn) + set(insn, InsnNode(Opcodes.ICONST_0)) + } + } + + private fun transformNullInstanceOfWithJump(insn: AbstractInsnNode) { + methodNode.instructions.run { + popReferenceValueBefore(insn) + val jump = insn.next.assertedCast { "JumpInsnNode expected" } + remove(insn) + if (jump.opcode == Opcodes.IFEQ) { + set(jump, JumpInsnNode(Opcodes.GOTO, jump.label)) + } + else { + remove(jump) + } + } + } + + private inner class NullabilityAssumptionsBuilder(val checkedReferenceTypes: Map) { + + private val checksDependingOnVariable = HashMap>() + + fun injectNullabilityAssumptions(): NullabilityAssumptions { + collectVariableDependentChecks() + return injectAssumptions() + } + + private fun collectVariableDependentChecks() { + for (insn in methodNode.instructions) { + if (insn.isInstanceOfOrNullCheck()) { + val previous = insn.previous ?: continue + if (previous.opcode == Opcodes.ALOAD) { + addDependentCheck(insn, previous as VarInsnNode) + } + else if (previous.opcode == Opcodes.DUP) { + val previous2 = previous.previous ?: continue + if (previous2.opcode == Opcodes.ALOAD) { + addDependentCheck(insn, previous2 as VarInsnNode) + } + } + } + else if (insn.isCheckParameterNotNull()) { + val ldcInsn = insn.previous ?: continue + if (ldcInsn.opcode != Opcodes.LDC) continue + val aLoadInsn = ldcInsn.previous ?: continue + if (aLoadInsn.opcode != Opcodes.ALOAD) continue + addDependentCheck(insn, aLoadInsn as VarInsnNode) + } + } + } + + private fun addDependentCheck(insn: AbstractInsnNode, aLoadInsn: VarInsnNode) { + checksDependingOnVariable.getOrPut(aLoadInsn.`var`) { + SmartList() + }.add(insn) + } + + private fun injectAssumptions(): NullabilityAssumptions { + val nullabilityAssumptions = NullabilityAssumptions() + for ((varIndex, dependentChecks) in checksDependingOnVariable) { + for (checkInsn in dependentChecks) { + val varType = checkedReferenceTypes[checkInsn] + ?: throw AssertionError("No var type @${checkInsn.getIndex()}") + nullabilityAssumptions.injectAssumptionsForCheck(varIndex, checkInsn, varType) + } + } + return nullabilityAssumptions + } + + private fun NullabilityAssumptions.injectAssumptionsForCheck(varIndex: Int, insn: AbstractInsnNode, varType: Type) { + when (insn.opcode) { + Opcodes.IFNULL, + Opcodes.IFNONNULL -> + injectAssumptionsForNullCheck(varIndex, insn as JumpInsnNode, varType) + Opcodes.INVOKESTATIC -> { + assert(insn.isCheckParameterNotNull()) { "Expected non-null parameter check @${insn.getIndex()}"} + injectAssumptionsForParameterNotNullCheck(varIndex, insn, varType) + } + Opcodes.INSTANCEOF -> + injectAssumptionsForInstanceOfCheck(varIndex, insn, varType) + } + } + + private fun NullabilityAssumptions.injectAssumptionsForNullCheck(varIndex: Int, insn: JumpInsnNode, varType: Type) { + // ALOAD v + // IFNULL L + // <...> -- v is not null here + // L: + // <...> -- v is null here + + val jumpsIfNull = insn.opcode == Opcodes.IFNULL + val originalLabel = insn.label + originalLabels[insn] = originalLabel + insn.label = synthetic(LabelNode(Label())) + + val insertAfterNull = if (jumpsIfNull) insn.label else insn + val insertAfterNonNull = if (jumpsIfNull) insn else insn.label + + methodNode.instructions.run { + add(insn.label) + + insert(insertAfterNull, listOfSynthetics { + aconst(null) + store(varIndex, varType) + if (jumpsIfNull) { + goTo(originalLabel.label) + } + }) + + insert(insertAfterNonNull, listOfSynthetics { + anew(varType) + store(varIndex, varType) + if (!jumpsIfNull) { + goTo(originalLabel.label) + } + }) + } + } + + private fun NullabilityAssumptions.injectAssumptionsForParameterNotNullCheck(varIndex: Int, insn: AbstractInsnNode, varType: Type) { + // ALOAD v + // LDC param_name + // INVOKESTATIC checkParameterIsNotNull + // <...> -- v is not null here (otherwise an exception was thrown) + + methodNode.instructions.insert(insn, listOfSynthetics { + anew(varType) + store(varIndex, varType) + }) + } + + private fun NullabilityAssumptions.injectAssumptionsForInstanceOfCheck(varIndex: Int, insn: AbstractInsnNode, varType: Type) { + // ALOAD v + // INSTANCEOF T + // IFEQ L + // <...> -- v is not null here (because it is an instance of T) + // L: + // <...> -- v is something else here (maybe null) + + val next = insn.next ?: return + if (next.opcode != Opcodes.IFEQ && next.opcode != Opcodes.IFNE) return + if (next !is JumpInsnNode) return + val jumpsIfInstance = next.opcode == Opcodes.IFNE + + val originalLabel: LabelNode? + val insertAfterNotNull: AbstractInsnNode + if (jumpsIfInstance) { + originalLabel = next.label + originalLabels[next] = next.label + val newLabel = synthetic(LabelNode(Label())) + methodNode.instructions.add(newLabel) + next.label = newLabel + insertAfterNotNull = newLabel + } + else { + originalLabel = null + insertAfterNotNull = next + } + + methodNode.instructions.run { + insert(insertAfterNotNull, listOfSynthetics { + anew(varType) + store(varIndex, varType) + if (originalLabel != null) { + goTo(originalLabel.label) + } + }) + } + } + + } + + inner class NullabilityAssumptions { + val originalLabels = HashMap() + val syntheticInstructions = ArrayList() + + fun synthetic(insn: T): T { + syntheticInstructions.add(insn) + return insn + } + + inline fun listOfSynthetics(block: InstructionAdapter.() -> Unit): InsnList { + val insnList = withInstructionAdapter(block) + for (insn in insnList) { + synthetic(insn) + } + return insnList + } + + fun revert() { + methodNode.instructions.run { + syntheticInstructions.forEach { remove(it) } + } + for ((jumpInsn, originalLabel) in originalLabels) { + jumpInsn.label = originalLabel + } + } + } + } +} + +internal fun AbstractInsnNode.isInstanceOfOrNullCheck() = + opcode == Opcodes.INSTANCEOF || opcode == Opcodes.IFNULL || opcode == Opcodes.IFNONNULL + +internal fun AbstractInsnNode.isCheckParameterNotNull() = + isInsn(Opcodes.INVOKESTATIC) { + owner == "kotlin/jvm/internal/Intrinsics" && + name == "checkParameterIsNotNull" && + desc == "(Ljava/lang/Object;Ljava/lang/String;)V" + } + +internal fun InsnList.popReferenceValueBefore(insn: AbstractInsnNode) { + val prev = insn.previous + when (prev?.opcode) { + Opcodes.ACONST_NULL, + Opcodes.DUP, + Opcodes.ALOAD -> + remove(prev) + else -> + insertBefore(insn, InsnNode(Opcodes.POP)) + } +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/nullabilityValues.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/nullabilityValues.kt index dad74cdd5f4..24ef3a5ffa2 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/nullabilityValues.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/nullabilityValues.kt @@ -16,12 +16,10 @@ package org.jetbrains.kotlin.codegen.optimization.nullCheck -import org.jetbrains.kotlin.codegen.optimization.boxing.isUnitInstance +import org.jetbrains.kotlin.codegen.optimization.boxing.ProgressionIteratorBasicValue import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue import org.jetbrains.kotlin.resolve.jvm.AsmTypes -import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Type -import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue class NotNullBasicValue(type: Type?) : StrictBasicValue(type) { @@ -37,3 +35,16 @@ class NotNullBasicValue(type: Type?) : StrictBasicValue(type) { object NullBasicValue : StrictBasicValue(AsmTypes.OBJECT_TYPE) +enum class Nullability { + NULL, NOT_NULL, NULLABLE; + fun isNull() = this == NULL + fun isNotNull() = this == NOT_NULL +} + +fun BasicValue.getNullability(): Nullability = + when (this) { + is NullBasicValue -> Nullability.NULL + is NotNullBasicValue -> Nullability.NOT_NULL + is ProgressionIteratorBasicValue -> Nullability.NOT_NULL + else -> Nullability.NULLABLE + } \ No newline at end of file diff --git a/compiler/testData/codegen/box/nullCheckOptimization/isNullable.kt b/compiler/testData/codegen/box/nullCheckOptimization/isNullable.kt new file mode 100644 index 00000000000..750f01972a0 --- /dev/null +++ b/compiler/testData/codegen/box/nullCheckOptimization/isNullable.kt @@ -0,0 +1,4 @@ +inline fun isNullable() = null is T + +fun box(): String = + if (isNullable()) "OK" else "Fail" \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/nullCheckOptimization/alreadyCheckedForIs.kt b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/alreadyCheckedForIs.kt new file mode 100644 index 00000000000..c2d247d898d --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/alreadyCheckedForIs.kt @@ -0,0 +1,7 @@ +fun test(x: String?) { + if (x !is String) return + + if (x == null) println("dead code") +} + +// 0 println \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/nullCheckOptimization/alreadyCheckedForNull.kt b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/alreadyCheckedForNull.kt new file mode 100644 index 00000000000..9611bc4d514 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/alreadyCheckedForNull.kt @@ -0,0 +1,7 @@ +fun test(x: String?) { + if (x == null) return + + if (x == null) println("dead code") +} + +// 0 println \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/nullCheckOptimization/kt12839.kt b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/kt12839.kt new file mode 100644 index 00000000000..bed00f82766 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/kt12839.kt @@ -0,0 +1,11 @@ +// WITH_RUNTIME + +fun test() { + val value = System.getProperty("key") + if (value != null) { + value.toUpperCase() + } +} + +// 1 IFNULL +// 0 IFNONNULL \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/nullCheckOptimization/notNullAsNotNullable.kt b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/notNullAsNotNullable.kt new file mode 100644 index 00000000000..beae25a1566 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/notNullAsNotNullable.kt @@ -0,0 +1,4 @@ +fun foo(s: String) = s as CharSequence + +// 0 IFNULL +// 0 IFNONNULL diff --git a/compiler/testData/codegen/bytecodeText/nullCheckOptimization/redundantSafeCall.kt b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/redundantSafeCall.kt new file mode 100644 index 00000000000..5a1fe4989db --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/redundantSafeCall.kt @@ -0,0 +1,6 @@ +fun test(s: String) = s?.length + +// 0 IFNULL +// 0 IFNONNULL +// 0 intValue +// 0 valueOf diff --git a/compiler/testData/codegen/bytecodeText/nullCheckOptimization/reifiedIs.kt b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/reifiedIs.kt new file mode 100644 index 00000000000..b227fd09320 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/reifiedIs.kt @@ -0,0 +1,4 @@ +inline fun Any?.isa() = this is T + +// 1 INSTANCEOF +// 1 reifiedOperationMarker diff --git a/compiler/testData/codegen/bytecodeText/nullCheckOptimization/reifiedNullIs.kt b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/reifiedNullIs.kt new file mode 100644 index 00000000000..530d6a816c7 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/reifiedNullIs.kt @@ -0,0 +1,4 @@ +inline fun isNullable() = null is T + +// 1 INSTANCEOF +// 1 reifiedOperationMarker \ No newline at end of file diff --git a/compiler/testData/codegen/light-analysis/nullCheckOptimization/isNullable.txt b/compiler/testData/codegen/light-analysis/nullCheckOptimization/isNullable.txt new file mode 100644 index 00000000000..5aa3ecf2e9a --- /dev/null +++ b/compiler/testData/codegen/light-analysis/nullCheckOptimization/isNullable.txt @@ -0,0 +1,5 @@ +@kotlin.Metadata +public final class IsNullableKt { + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String + private final static method isNullable(): boolean +} diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 51a21bcb6db..ed072d53368 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -11021,6 +11021,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/nullCheckOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); } + @TestMetadata("isNullable.kt") + public void testIsNullable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/nullCheckOptimization/isNullable.kt"); + doTest(fileName); + } + @TestMetadata("kt7774.kt") public void testKt7774() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/nullCheckOptimization/kt7774.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index ceec6430086..6efe697517e 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -11021,6 +11021,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/nullCheckOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); } + @TestMetadata("isNullable.kt") + public void testIsNullable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/nullCheckOptimization/isNullable.kt"); + doTest(fileName); + } + @TestMetadata("kt7774.kt") public void testKt7774() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/nullCheckOptimization/kt7774.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index b7952e69c67..eef5708288d 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -1634,6 +1634,18 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/nullCheckOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); } + @TestMetadata("alreadyCheckedForIs.kt") + public void testAlreadyCheckedForIs() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/alreadyCheckedForIs.kt"); + doTest(fileName); + } + + @TestMetadata("alreadyCheckedForNull.kt") + public void testAlreadyCheckedForNull() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/alreadyCheckedForNull.kt"); + doTest(fileName); + } + @TestMetadata("ifNullEqualsNull.kt") public void testIfNullEqualsNull() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifNullEqualsNull.kt"); @@ -1657,6 +1669,36 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifUnitEqualsNullInline.kt"); doTest(fileName); } + + @TestMetadata("kt12839.kt") + public void testKt12839() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/kt12839.kt"); + doTest(fileName); + } + + @TestMetadata("notNullAsNotNullable.kt") + public void testNotNullAsNotNullable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/notNullAsNotNullable.kt"); + doTest(fileName); + } + + @TestMetadata("redundantSafeCall.kt") + public void testRedundantSafeCall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/redundantSafeCall.kt"); + doTest(fileName); + } + + @TestMetadata("reifiedIs.kt") + public void testReifiedIs() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/reifiedIs.kt"); + doTest(fileName); + } + + @TestMetadata("reifiedNullIs.kt") + public void testReifiedNullIs() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/reifiedNullIs.kt"); + doTest(fileName); + } } @TestMetadata("compiler/testData/codegen/bytecodeText/ranges") diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index 0dd3d7c1c17..6d5ac301d8a 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -12433,6 +12433,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/nullCheckOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true); } + @TestMetadata("isNullable.kt") + public void testIsNullable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/nullCheckOptimization/isNullable.kt"); + doTest(fileName); + } + @TestMetadata("kt7774.kt") public void testKt7774() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/nullCheckOptimization/kt7774.kt");