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:
@@ -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
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+16
-23
@@ -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))
|
||||
|
||||
+20
-2
@@ -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;
|
||||
|
||||
+9
-6
@@ -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"
|
||||
|
||||
-81
@@ -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;
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+106
@@ -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)
|
||||
|
||||
+109
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+11
-25
@@ -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"
|
||||
}
|
||||
+18
@@ -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
|
||||
+19
@@ -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()
|
||||
+18
@@ -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
|
||||
+18
@@ -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
|
||||
}
|
||||
+15
@@ -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)
|
||||
|
||||
+15
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user