diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java index a4a5e6c0fdb..4e00f245613 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java @@ -52,6 +52,7 @@ import org.jetbrains.kotlin.util.OperatorNameConventions; import org.jetbrains.org.objectweb.asm.*; import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter; import org.jetbrains.org.objectweb.asm.tree.*; +import org.jetbrains.org.objectweb.asm.util.Printer; import org.jetbrains.org.objectweb.asm.util.Textifier; import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor; @@ -390,6 +391,11 @@ public class InlineCodegenUtil { return sw.toString().trim(); } + @NotNull + public static String getInsnOpcodeText(@Nullable AbstractInsnNode node) { + return node == null ? "null" : Printer.OPCODES[node.getOpcode()]; + } + @NotNull /* package */ static ClassReader buildClassReaderByInternalName(@NotNull GenerationState state, @NotNull String internalName) { //try to find just compiled classes then in dependencies 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 13e067f51d6..96a37c897e6 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.java @@ -21,8 +21,8 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.codegen.TransformationMethodVisitor; import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantBoxingMethodTransformer; import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantCoercionToUnitTransformer; -import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantNullCheckMethodTransformer; import org.jetbrains.kotlin.codegen.optimization.common.UtilKt; +import org.jetbrains.kotlin.codegen.optimization.nullCheck.RedundantNullCheckMethodTransformer; import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer; import org.jetbrains.org.objectweb.asm.MethodVisitor; import org.jetbrains.org.objectweb.asm.tree.MethodNode; diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxingInterpreter.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxingInterpreter.kt index 2bc100b0112..650d7a1bee0 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxingInterpreter.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxingInterpreter.kt @@ -35,14 +35,12 @@ import java.util.* open class BoxingInterpreter(private val insnList: InsnList) : OptimizationBasicInterpreter() { private val boxingPlaces = HashMap() - protected open fun createNewBoxing(insn: AbstractInsnNode, type: Type, progressionIterator: ProgressionIteratorBasicValue?): BasicValue { - val index = insnList.indexOf(insn) - return boxingPlaces.getOrPut(index) { - val boxedBasicValue = CleanBoxedValue(type, insn, progressionIterator) - onNewBoxedValue(boxedBasicValue) - boxedBasicValue - } - } + protected open fun createNewBoxing(insn: AbstractInsnNode, type: Type, progressionIterator: ProgressionIteratorBasicValue?): BasicValue = + boxingPlaces.getOrPut(insnList.indexOf(insn)) { + val boxedBasicValue = CleanBoxedValue(type, insn, progressionIterator) + onNewBoxedValue(boxedBasicValue) + boxedBasicValue + } protected fun checkUsedValue(value: BasicValue) { if (value is TaintedBoxedValue) { @@ -66,9 +64,8 @@ open class BoxingInterpreter(private val insnList: InsnList) : OptimizationBasic onUnboxing(insn, firstArg, value.type) value } - insn.isIteratorMethodCallOfProgression(values) -> { - ProgressionIteratorBasicValue(getValuesTypeOfProgressionClass(firstArg.type.internalName)) - } + insn.isIteratorMethodCallOfProgression(values) -> + ProgressionIteratorBasicValue.byProgressionClassType(firstArg.type) insn.isNextMethodCallOfProgressionIterator(values) -> { val progressionIterator = firstArg as? ProgressionIteratorBasicValue ?: throw AssertionError("firstArg should be progression iterator") @@ -100,7 +97,7 @@ open class BoxingInterpreter(private val insnList: InsnList) : OptimizationBasic protected open fun isExactValue(value: BasicValue) = value is ProgressionIteratorBasicValue || value is CleanBoxedValue || - value.type != null && isProgressionClass(value.type.internalName) + value.type != null && isProgressionClass(value.type) override fun merge(v: BasicValue, w: BasicValue) = when { @@ -190,23 +187,19 @@ private fun AbstractInsnNode.isJavaLangClassBoxing() = desc == JLCLASS_TO_KCLASS } -private fun AbstractInsnNode.isNextMethodCallOfProgressionIterator(values: List) = - values[0] is ProgressionIteratorBasicValue && +fun AbstractInsnNode.isNextMethodCallOfProgressionIterator(values: List) = + values.firstOrNull() is ProgressionIteratorBasicValue && isMethodInsnWith(Opcodes.INVOKEINTERFACE) { name == "next" } -private fun AbstractInsnNode.isIteratorMethodCallOfProgression(values: List) = +fun AbstractInsnNode.isIteratorMethodCallOfProgression(values: List) = isMethodInsnWith(Opcodes.INVOKEINTERFACE) { - val firstArgType = values[0].type + val firstArgType = values.firstOrNull()?.type firstArgType != null && - isProgressionClass(firstArgType.internalName) && + isProgressionClass(firstArgType) && name == "iterator" } -private fun isProgressionClass(internalClassName: String) = - RangeCodegenUtil.isRangeOrProgression(buildFqNameByInternal(internalClassName)) - -private fun getValuesTypeOfProgressionClass(progressionClassInternalName: String) = - RangeCodegenUtil.getPrimitiveRangeOrProgressionElementType(buildFqNameByInternal(progressionClassInternalName)) - ?.typeName?.asString() ?: error("type should be not null") +fun isProgressionClass(type: Type) = + RangeCodegenUtil.isRangeOrProgression(buildFqNameByInternal(type.internalName)) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/ProgressionIteratorBasicValue.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/ProgressionIteratorBasicValue.java index 68d233d96ae..f27a1f24f06 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/ProgressionIteratorBasicValue.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/ProgressionIteratorBasicValue.java @@ -18,17 +18,18 @@ package org.jetbrains.kotlin.codegen.optimization.boxing; import com.google.common.collect.ImmutableMap; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.builtins.PrimitiveType; import org.jetbrains.kotlin.codegen.RangeCodegenUtil; import org.jetbrains.kotlin.codegen.intrinsics.IteratorNext; import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue; +import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType; import org.jetbrains.org.objectweb.asm.Type; public class ProgressionIteratorBasicValue extends StrictBasicValue { private final static ImmutableMap VALUES_TYPENAME_TO_TYPE; - static { ImmutableMap.Builder builder = ImmutableMap.builder(); for (PrimitiveType primitiveType : RangeCodegenUtil.supportedRangeTypes()) { @@ -37,6 +38,15 @@ public class ProgressionIteratorBasicValue extends StrictBasicValue { VALUES_TYPENAME_TO_TYPE = builder.build(); } + private static final ImmutableMap ITERATOR_VALUE_BY_ELEMENT_PRIMITIVE_TYPE; + static { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (PrimitiveType elementType : RangeCodegenUtil.supportedRangeTypes()) { + builder.put(elementType, new ProgressionIteratorBasicValue(elementType.getTypeName().asString())); + } + ITERATOR_VALUE_BY_ELEMENT_PRIMITIVE_TYPE = builder.build(); + } + @NotNull private static Type getValuesType(@NotNull String valuesTypeName) { Type type = VALUES_TYPENAME_TO_TYPE.get(valuesTypeName); @@ -47,12 +57,20 @@ public class ProgressionIteratorBasicValue extends StrictBasicValue { private final Type valuesPrimitiveType; private final String valuesPrimitiveTypeName; - public ProgressionIteratorBasicValue(@NotNull String valuesPrimitiveTypeName) { + private ProgressionIteratorBasicValue(@NotNull String valuesPrimitiveTypeName) { super(IteratorNext.Companion.getPrimitiveIteratorType(Name.identifier(valuesPrimitiveTypeName))); this.valuesPrimitiveType = getValuesType(valuesPrimitiveTypeName); this.valuesPrimitiveTypeName = valuesPrimitiveTypeName; } + + @Nullable + public static ProgressionIteratorBasicValue byProgressionClassType(@NotNull Type progressionClassType) { + FqName classFqName = new FqName(progressionClassType.getClassName()); + PrimitiveType elementType = RangeCodegenUtil.getPrimitiveRangeOrProgressionElementType(classFqName); + return ITERATOR_VALUE_BY_ELEMENT_PRIMITIVE_TYPE.get(elementType); + } + @NotNull public Type getValuesPrimitiveType() { return valuesPrimitiveType; diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantCoercionToUnitTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantCoercionToUnitTransformer.kt index 81d862b956f..32af60cad84 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantCoercionToUnitTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantCoercionToUnitTransformer.kt @@ -63,7 +63,7 @@ class RedundantCoercionToUnitTransformer : MethodTransformer() { private val frames by lazy { analyzeMethodBody() } fun transform() { - if (!insns.any { it.isUnitOrNull() }) return + if (!insns.any { it.isUnitInstanceOrNull() }) return computeTransformations() for ((insn, transformation) in transformations.entries) { @@ -158,7 +158,7 @@ class RedundantCoercionToUnitTransformer : MethodTransformer() { transformations[insn] = replaceWithPopTransformation(boxedValueSize) } - insn.isUnitOrNull() -> { + insn.isUnitInstanceOrNull() -> { transformations[insn] = replaceWithNopTransformation() } @@ -232,7 +232,7 @@ class RedundantCoercionToUnitTransformer : MethodTransformer() { it.isPrimitiveBoxing() && (it as MethodInsnNode).owner == resultType private fun isTransformablePopOperand(insn: AbstractInsnNode) = - insn.opcode == Opcodes.CHECKCAST || insn.isPrimitiveBoxing() || insn.isUnitOrNull() + insn.opcode == Opcodes.CHECKCAST || insn.isPrimitiveBoxing() || insn.isUnitInstanceOrNull() private fun isDontTouch(insn: AbstractInsnNode) = dontTouchInsnIndices[insnList.indexOf(insn)] @@ -240,6 +240,9 @@ class RedundantCoercionToUnitTransformer : MethodTransformer() { } -fun AbstractInsnNode.isUnitOrNull() = - opcode == Opcodes.ACONST_NULL || - opcode == Opcodes.GETSTATIC && this is FieldInsnNode && owner == "kotlin/Unit" && name == "INSTANCE" +fun AbstractInsnNode.isUnitInstanceOrNull() = + opcode == Opcodes.ACONST_NULL || isUnitInstance() + +fun AbstractInsnNode.isUnitInstance() = + opcode == Opcodes.GETSTATIC && + this is FieldInsnNode && owner == "kotlin/Unit" && name == "INSTANCE" diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantNullCheckMethodTransformer.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantNullCheckMethodTransformer.java deleted file mode 100644 index b05c34b9663..00000000000 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantNullCheckMethodTransformer.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2010-2015 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.boxing; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer; -import org.jetbrains.org.objectweb.asm.Opcodes; -import org.jetbrains.org.objectweb.asm.tree.*; -import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue; -import org.jetbrains.org.objectweb.asm.tree.analysis.Frame; - -import java.util.ArrayList; -import java.util.List; - -public class RedundantNullCheckMethodTransformer extends MethodTransformer { - - @Override - public void transform(@NotNull String internalClassName, @NotNull MethodNode methodNode) { - while (removeRedundantNullCheckPass(internalClassName, methodNode)) { - //do nothing - } - } - - private static boolean removeRedundantNullCheckPass(@NotNull String internalClassName, @NotNull MethodNode methodNode) { - InsnList insnList = methodNode.instructions; - Frame[] frames = analyze( - internalClassName, methodNode, - new NullabilityInterpreter(insnList) - ); - - List insnsToOptimize = new ArrayList(); - - for (int i = 0; i < insnList.size(); i++) { - Frame frame = frames[i]; - AbstractInsnNode insn = insnList.get(i); - - if ((insn.getOpcode() == Opcodes.IFNULL || insn.getOpcode() == Opcodes.IFNONNULL) && - frame != null && frame.getStack(frame.getStackSize() - 1) instanceof NotNullBasicValue) { - insnsToOptimize.add(insn); - } - } - - for (AbstractInsnNode insn : insnsToOptimize) { - if (insn.getPrevious() != null && insn.getPrevious().getOpcode() == Opcodes.DUP) { - insnList.remove(insn.getPrevious()); - } - else { - insnList.insertBefore(insn, new InsnNode(Opcodes.POP)); - } - - assert insn.getOpcode() == Opcodes.IFNULL - || insn.getOpcode() == Opcodes.IFNONNULL : "only IFNULL/IFNONNULL are supported"; - - if (insn.getOpcode() == Opcodes.IFNULL) { - insnList.remove(insn); - } - else { - insnList.set( - insn, - new JumpInsnNode(Opcodes.GOTO, ((JumpInsnNode) insn).label) - ); - } - } - - return insnsToOptimize.size() > 0; - } -} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/OptimizationBasicInterpreter.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/OptimizationBasicInterpreter.java index 281dcec21ed..8ca813e02b5 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/OptimizationBasicInterpreter.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/OptimizationBasicInterpreter.java @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.codegen.optimization.common; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.codegen.AsmUtil; +import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil; import org.jetbrains.org.objectweb.asm.Handle; import org.jetbrains.org.objectweb.asm.Opcodes; import org.jetbrains.org.objectweb.asm.Type; @@ -136,7 +137,7 @@ public class OptimizationBasicInterpreter extends Interpreter implem case NEW: return newValue(Type.getObjectType(((TypeInsnNode) insn).desc)); default: - throw new Error("Internal error."); + throw new IllegalArgumentException("Unexpected instruction: " + InlineCodegenUtil.getInsnOpcodeText(insn)); } } @@ -221,7 +222,7 @@ public class OptimizationBasicInterpreter extends Interpreter implem case PUTFIELD: return null; default: - throw new Error("Internal error."); + throw new IllegalArgumentException("Unexpected instruction: " + InlineCodegenUtil.getInsnOpcodeText(insn)); } } @@ -340,7 +341,7 @@ public class OptimizationBasicInterpreter extends Interpreter implem case IFNONNULL: return null; default: - throw new Error("Internal error."); + throw new IllegalArgumentException("Unexpected instruction: " + InlineCodegenUtil.getInsnOpcodeText(insn)); } } 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 new file mode 100644 index 00000000000..0486c4d0030 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/NullabilityInterpreter.kt @@ -0,0 +1,106 @@ +/* + * 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.optimization.boxing.* +import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter +import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue +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.TypeInsnNode +import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue + +class NullabilityV2Interpreter : OptimizationBasicInterpreter() { + override fun newOperation(insn: AbstractInsnNode): BasicValue? { + val defaultResult = super.newOperation(insn) + val resultType = defaultResult?.type + + return when { + insn.opcode == Opcodes.ACONST_NULL -> + NullBasicValue + insn.opcode == Opcodes.NEW -> + NotNullBasicValue(resultType) + insn.opcode == Opcodes.LDC && resultType.isReferenceType() -> + NotNullBasicValue(resultType) + insn.isUnitInstance() -> + NotNullBasicValue(resultType) + else -> + defaultResult + } + } + + private fun Type?.isReferenceType() = + this?.sort.let { it == Type.OBJECT || it == Type.ARRAY } + + override fun unaryOperation(insn: AbstractInsnNode, value: BasicValue?): BasicValue? { + val defaultResult = super.unaryOperation(insn, value) + val resultType = defaultResult?.type + + return when { + insn.opcode == Opcodes.CHECKCAST -> + value + insn.opcode == Opcodes.NEWARRAY || insn.opcode == Opcodes.ANEWARRAY -> + NotNullBasicValue(resultType) + else -> + defaultResult + } + } + + override fun naryOperation(insn: AbstractInsnNode, values: List): BasicValue? { + val defaultResult = super.naryOperation(insn, values) + val resultType = defaultResult?.type + + return when { + insn.isBoxing() -> + NotNullBasicValue(resultType) + insn.isIteratorMethodCallOfProgression(values) -> + ProgressionIteratorBasicValue.byProgressionClassType(values[0].type) + insn.isNextMethodCallOfProgressionIterator(values) -> + NotNullBasicValue(resultType) + else -> + defaultResult + } + } + + override fun merge(v: BasicValue, w: BasicValue): BasicValue = + when { + v is NullBasicValue && w is NullBasicValue -> + NullBasicValue + v is NullBasicValue || w is NullBasicValue -> + StrictBasicValue.REFERENCE_VALUE + v is ProgressionIteratorBasicValue && w is ProgressionIteratorBasicValue -> + mergeNotNullValuesOfSameKind(v, w) + v is ProgressionIteratorBasicValue && w is NotNullBasicValue -> + NotNullBasicValue.NOT_NULL_REFERENCE_VALUE + w is ProgressionIteratorBasicValue && v is NotNullBasicValue -> + NotNullBasicValue.NOT_NULL_REFERENCE_VALUE + v is NotNullBasicValue && w is NotNullBasicValue -> + mergeNotNullValuesOfSameKind(v, w) + else -> + super.merge(v, w) + } + + private fun mergeNotNullValuesOfSameKind(v: StrictBasicValue, w: StrictBasicValue) = + if (v.type == w.type) v else NotNullBasicValue.NOT_NULL_REFERENCE_VALUE + +} + + +fun TypeInsnNode.getObjectType(): Type = + Type.getObjectType(desc) + 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 new file mode 100644 index 00000000000..7a3802631ee --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt @@ -0,0 +1,109 @@ +/* + * 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.optimization.DeadCodeEliminationMethodTransformer +import org.jetbrains.kotlin.codegen.optimization.boxing.ProgressionIteratorBasicValue +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.safeAs +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.tree.InsnNode +import org.jetbrains.org.objectweb.asm.tree.JumpInsnNode +import org.jetbrains.org.objectweb.asm.tree.MethodNode +import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue + +class RedundantNullCheckMethodTransformer : MethodTransformer() { + private val deadCodeElimination = DeadCodeEliminationMethodTransformer() + + override fun transform(internalClassName: String, methodNode: MethodNode) { + while (runSingleNullCheckEliminationPass(internalClassName, methodNode)) { + deadCodeElimination.transform(internalClassName, methodNode) + } + } + + 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) + + private fun isAlwaysTrue(opcode: Int, nullability: Nullability) = + (opcode == Opcodes.IFNULL && nullability == Nullability.NULL) || + (opcode == Opcodes.IFNONNULL && nullability == Nullability.NOT_NULL) + + + private fun runSingleNullCheckEliminationPass(internalClassName: String, methodNode: MethodNode): Boolean { + val insnList = methodNode.instructions + val instructions = insnList.toArray() + + val nullCheckIfs = instructions.mapNotNullTo(SmartList()) { + it.safeAs()?.takeIf { + it.opcode == Opcodes.IFNULL || + it.opcode == Opcodes.IFNONNULL + } + } + if (nullCheckIfs.isEmpty()) return false + + val frames = analyze(internalClassName, methodNode, NullabilityV2Interpreter()) + + val redundantNullCheckIfs = nullCheckIfs.mapNotNull { insn -> + frames[instructions.indexOf(insn)]?.top()?.let { top -> + val nullability = top.getNullability() + if (nullability == Nullability.NULLABLE) + null + else + Pair(insn, nullability) + } + } + if (redundantNullCheckIfs.isEmpty()) return false + + for ((insn, nullability) in redundantNullCheckIfs) { + val previous = insn.previous + when (previous?.opcode) { + Opcodes.ALOAD, Opcodes.DUP -> + insnList.remove(previous) + else -> + insnList.insert(previous, InsnNode(Opcodes.POP)) + } + + when { + isAlwaysTrue(insn.opcode, nullability) -> + insnList.set(insn, JumpInsnNode(Opcodes.GOTO, insn.label)) + isAlwaysFalse(insn.opcode, nullability) -> + insnList.remove(insn) + } + } + + return true + } +} + + + + diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/NullabilityInterpreter.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/nullabilityValues.kt similarity index 52% rename from compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/NullabilityInterpreter.kt rename to compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/nullabilityValues.kt index e596d7e1909..dad74cdd5f4 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/NullabilityInterpreter.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/nullabilityValues.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2014 JetBrains s.r.o. + * 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. @@ -14,40 +14,26 @@ * limitations under the License. */ -package org.jetbrains.kotlin.codegen.optimization.boxing +package org.jetbrains.kotlin.codegen.optimization.nullCheck +import org.jetbrains.kotlin.codegen.optimization.boxing.isUnitInstance 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.InsnList import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue -class NullabilityInterpreter(insns: InsnList) : BoxingInterpreter(insns) { - override fun unaryOperation(insn: AbstractInsnNode, value: BasicValue) = makeNotNullIfNeeded(insn, super.unaryOperation(insn, value)) - - override fun newOperation(insn: AbstractInsnNode) = makeNotNullIfNeeded(insn, super.newOperation(insn)) - - override fun isExactValue(value: BasicValue) = super.isExactValue(value) || value is NotNullBasicValue - - override fun createNewBoxing(insn: AbstractInsnNode, type: Type, progressionIterator: ProgressionIteratorBasicValue?) = - NotNullBasicValue(type) -} - -private fun makeNotNullIfNeeded(insn: AbstractInsnNode, value: BasicValue?): BasicValue? = - when (insn.opcode) { - Opcodes.ANEWARRAY, Opcodes.NEWARRAY, Opcodes.LDC, Opcodes.NEW -> - if (value?.type?.sort == Type.OBJECT || value?.type?.sort == Type.ARRAY) - NotNullBasicValue(value.type) - else - value - - else -> value - } - class NotNullBasicValue(type: Type?) : StrictBasicValue(type) { override fun equals(other: Any?): Boolean = other is NotNullBasicValue // We do not differ not-nullable values, so we should always return the same hashCode // Actually it doesn't really matter because analyzer is not supposed to store values in hashtables override fun hashCode() = 0 + + companion object { + val NOT_NULL_REFERENCE_VALUE = NotNullBasicValue(StrictBasicValue.REFERENCE_VALUE.type) + } } + +object NullBasicValue : StrictBasicValue(AsmTypes.OBJECT_TYPE) + diff --git a/compiler/testData/codegen/box/nullCheckOptimization/kt7774.kt b/compiler/testData/codegen/box/nullCheckOptimization/kt7774.kt new file mode 100644 index 00000000000..ce35467e7d7 --- /dev/null +++ b/compiler/testData/codegen/box/nullCheckOptimization/kt7774.kt @@ -0,0 +1,12 @@ +var flag = false + +inline fun foo(c: String? = null) { + if (c != null) { + flag = true + } +} + +fun box(): String { + foo() + return if (flag) "fail" else "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifNullEqualsNull.kt b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifNullEqualsNull.kt new file mode 100644 index 00000000000..1e4a4758cd8 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifNullEqualsNull.kt @@ -0,0 +1,18 @@ +// WITH_RUNTIME + +fun test1() { + val a = null + + if (a != null) { + println("X1") + } + + if (a == null) { + println("X2") + } +} + +// 0 IFNULL +// 0 IFNONNULL +// 0 X1 +// 1 X2 \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifNullEqualsNullInline.kt b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifNullEqualsNullInline.kt new file mode 100644 index 00000000000..cd4478fc0c0 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifNullEqualsNullInline.kt @@ -0,0 +1,19 @@ +// WITH_RUNTIME + +// FILE: test.kt + +fun test1() { + val n = null + n.elvis { "X1" } + "X2".elvis { "X3" } +} + +// @TestKt.class: +// 0 IFNULL +// 0 IFNONNULL +// 1 X1 +// 1 X2 +// 0 X3 + +// FILE: inline.kt +inline fun T?.elvis(rhs: () -> T): T = this ?: rhs() \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifUnitEqualsNull.kt b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifUnitEqualsNull.kt new file mode 100644 index 00000000000..4642a4c399a --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifUnitEqualsNull.kt @@ -0,0 +1,18 @@ +// WITH_RUNTIME + +fun test1() { + val a = Unit + + if (a != null) { + println("X1") + } + + if (a == null) { + println("X2") + } +} + +// 0 IFNULL +// 0 IFNONNULL +// 1 X1 +// 0 X2 \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifUnitEqualsNullInline.kt b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifUnitEqualsNullInline.kt new file mode 100644 index 00000000000..25a60c4f71a --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifUnitEqualsNullInline.kt @@ -0,0 +1,18 @@ +// WITH_RUNTIME + +// FILE: test.kt + +fun test1() { + val u = Unit + u.mapNullable({ "X1" }, { "X2" }) +} + +// @TestKt.class: +// 0 IFNULL +// 0 IFNONNULL +// 1 X1 +// 0 X2 + +// FILE: inline.kt +inline fun T?.mapNullable(ifNotNull: (T) -> R, ifNull: () -> R) = + if (this == null) ifNull() else ifNotNull(this) \ No newline at end of file diff --git a/compiler/testData/codegen/light-analysis/nullCheckOptimization/kt7774.txt b/compiler/testData/codegen/light-analysis/nullCheckOptimization/kt7774.txt new file mode 100644 index 00000000000..9095aa08927 --- /dev/null +++ b/compiler/testData/codegen/light-analysis/nullCheckOptimization/kt7774.txt @@ -0,0 +1,9 @@ +@kotlin.Metadata +public final class Kt7774Kt { + private static field flag: boolean + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String + public synthetic static method foo$default(p0: java.lang.String, p1: int, p2: java.lang.Object): void + public final static method foo(@org.jetbrains.annotations.Nullable p0: java.lang.String): void + public final static method getFlag(): boolean + public final static method setFlag(p0: boolean): void +} 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 8a4770dca4f..30659cdc68c 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 @@ -10956,6 +10956,21 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes } } + @TestMetadata("compiler/testData/codegen/box/nullCheckOptimization") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class NullCheckOptimization extends AbstractIrBlackBoxCodegenTest { + public void testAllFilesPresentInNullCheckOptimization() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/nullCheckOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("kt7774.kt") + public void testKt7774() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/nullCheckOptimization/kt7774.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/box/objectIntrinsics") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 6031d2b43c3..2c8fa254c22 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -10956,6 +10956,21 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { } } + @TestMetadata("compiler/testData/codegen/box/nullCheckOptimization") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class NullCheckOptimization extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInNullCheckOptimization() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/nullCheckOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("kt7774.kt") + public void testKt7774() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/nullCheckOptimization/kt7774.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/box/objectIntrinsics") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index 0ada6388345..753565a7adf 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -1569,6 +1569,39 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { } } + @TestMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class NullCheckOptimization extends AbstractBytecodeTextTest { + public void testAllFilesPresentInNullCheckOptimization() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/nullCheckOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("ifNullEqualsNull.kt") + public void testIfNullEqualsNull() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifNullEqualsNull.kt"); + doTest(fileName); + } + + @TestMetadata("ifNullEqualsNullInline.kt") + public void testIfNullEqualsNullInline() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifNullEqualsNullInline.kt"); + doTest(fileName); + } + + @TestMetadata("ifUnitEqualsNull.kt") + public void testIfUnitEqualsNull() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifUnitEqualsNull.kt"); + doTest(fileName); + } + + @TestMetadata("ifUnitEqualsNullInline.kt") + public void testIfUnitEqualsNullInline() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifUnitEqualsNullInline.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/bytecodeText/ranges") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) 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 009ddf14154..6595f1ebc14 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 @@ -12368,6 +12368,21 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { } } + @TestMetadata("compiler/testData/codegen/box/nullCheckOptimization") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class NullCheckOptimization extends AbstractJsCodegenBoxTest { + public void testAllFilesPresentInNullCheckOptimization() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/nullCheckOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true); + } + + @TestMetadata("kt7774.kt") + public void testKt7774() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/nullCheckOptimization/kt7774.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/box/objectIntrinsics") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)