Make redundant null check optimization independent of boxing optimization algorithm.

Run DCE after each single redundant null check optimization pass.
This commit is contained in:
Dmitry Petrov
2017-02-06 20:01:54 +03:00
parent eda43c8b45
commit 3fc106572e
20 changed files with 454 additions and 141 deletions
@@ -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
@@ -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;
@@ -35,14 +35,12 @@ import java.util.*
open class BoxingInterpreter(private val insnList: InsnList) : OptimizationBasicInterpreter() {
private val boxingPlaces = HashMap<Int, BoxedBasicValue>()
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<BasicValue>) =
values[0] is ProgressionIteratorBasicValue &&
fun AbstractInsnNode.isNextMethodCallOfProgressionIterator(values: List<BasicValue>) =
values.firstOrNull() is ProgressionIteratorBasicValue &&
isMethodInsnWith(Opcodes.INVOKEINTERFACE) {
name == "next"
}
private fun AbstractInsnNode.isIteratorMethodCallOfProgression(values: List<BasicValue>) =
fun AbstractInsnNode.isIteratorMethodCallOfProgression(values: List<BasicValue>) =
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))
@@ -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<String, Type> VALUES_TYPENAME_TO_TYPE;
static {
ImmutableMap.Builder<String, Type> 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<PrimitiveType, ProgressionIteratorBasicValue> ITERATOR_VALUE_BY_ELEMENT_PRIMITIVE_TYPE;
static {
ImmutableMap.Builder<PrimitiveType, ProgressionIteratorBasicValue> 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;
@@ -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"
@@ -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<BasicValue>[] frames = analyze(
internalClassName, methodNode,
new NullabilityInterpreter(insnList)
);
List<AbstractInsnNode> insnsToOptimize = new ArrayList<AbstractInsnNode>();
for (int i = 0; i < insnList.size(); i++) {
Frame<BasicValue> 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;
}
}
@@ -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<BasicValue> 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<BasicValue> 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<BasicValue> implem
case IFNONNULL:
return null;
default:
throw new Error("Internal error.");
throw new IllegalArgumentException("Unexpected instruction: " + InlineCodegenUtil.getInsnOpcodeText(insn));
}
}
@@ -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>): 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)
@@ -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<JumpInsnNode>()) {
it.safeAs<JumpInsnNode>()?.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
}
}
@@ -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)
@@ -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"
}
@@ -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
@@ -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 : Any> T?.elvis(rhs: () -> T): T = this ?: rhs()
@@ -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
@@ -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 : Any, R> T?.mapNullable(ifNotNull: (T) -> R, ifNull: () -> R) =
if (this == null) ifNull() else ifNotNull(this)
@@ -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
}
@@ -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)
@@ -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)
@@ -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)
@@ -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)