Optimize coercion to Unit.
POP operations are backward-propagated. Operation can't be transformed if its result is moved within stack (by DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2, or SWAP). Unnecessary operations are replaced with NOPs (for debugger). Unnecessary NOPs are removed. KT-9922 Suboptimal generation for simple safe call with unused return value KT-11116 Optimize methods returning Unit
This commit is contained in:
+2
-2
@@ -150,13 +150,13 @@ class LabelNormalizationMethodTransformer : MethodTransformer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun InsnList.replaceNodeGetNext(oldNode: AbstractInsnNode, newNode: AbstractInsnNode): AbstractInsnNode? {
|
fun InsnList.replaceNodeGetNext(oldNode: AbstractInsnNode, newNode: AbstractInsnNode): AbstractInsnNode? {
|
||||||
insertBefore(oldNode, newNode)
|
insertBefore(oldNode, newNode)
|
||||||
remove(oldNode)
|
remove(oldNode)
|
||||||
return newNode.next
|
return newNode.next
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun InsnList.removeNodeGetNext(oldNode: AbstractInsnNode): AbstractInsnNode? {
|
fun InsnList.removeNodeGetNext(oldNode: AbstractInsnNode): AbstractInsnNode? {
|
||||||
val next = oldNode.next
|
val next = oldNode.next
|
||||||
remove(oldNode)
|
remove(oldNode)
|
||||||
return next
|
return next
|
||||||
|
|||||||
+3
-1
@@ -20,6 +20,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil;
|
import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil;
|
||||||
import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantBoxingMethodTransformer;
|
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.boxing.RedundantNullCheckMethodTransformer;
|
||||||
import org.jetbrains.kotlin.codegen.optimization.common.UtilKt;
|
import org.jetbrains.kotlin.codegen.optimization.common.UtilKt;
|
||||||
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer;
|
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer;
|
||||||
@@ -42,7 +43,8 @@ public class OptimizationMethodVisitor extends MethodVisitor {
|
|||||||
new RedundantNullCheckMethodTransformer(),
|
new RedundantNullCheckMethodTransformer(),
|
||||||
new RedundantBoxingMethodTransformer(),
|
new RedundantBoxingMethodTransformer(),
|
||||||
new DeadCodeEliminationMethodTransformer(),
|
new DeadCodeEliminationMethodTransformer(),
|
||||||
new RedundantGotoMethodTransformer()
|
new RedundantGotoMethodTransformer(),
|
||||||
|
new RedundantCoercionToUnitTransformer()
|
||||||
};
|
};
|
||||||
|
|
||||||
private final MethodNode methodNode;
|
private final MethodNode methodNode;
|
||||||
|
|||||||
+71
-74
@@ -117,89 +117,86 @@ open class BoxingInterpreter(private val insnList: InsnList) : OptimizationBasic
|
|||||||
protected open fun onMergeFail(value: BoxedBasicValue) {}
|
protected open fun onMergeFail(value: BoxedBasicValue) {}
|
||||||
protected open fun onMergeSuccess(v: BoxedBasicValue, w: BoxedBasicValue) {}
|
protected open fun onMergeSuccess(v: BoxedBasicValue, w: BoxedBasicValue) {}
|
||||||
|
|
||||||
companion object {
|
}
|
||||||
private val UNBOXING_METHOD_NAMES =
|
|
||||||
ImmutableSet.of("booleanValue", "charValue", "byteValue", "shortValue", "intValue", "floatValue", "longValue", "doubleValue")
|
|
||||||
|
|
||||||
private val KCLASS_TO_JLCLASS = Type.getMethodDescriptor(AsmTypes.JAVA_CLASS_TYPE, AsmTypes.K_CLASS_TYPE)
|
private val UNBOXING_METHOD_NAMES =
|
||||||
private val JLCLASS_TO_KCLASS = Type.getMethodDescriptor(AsmTypes.K_CLASS_TYPE, AsmTypes.JAVA_CLASS_TYPE)
|
ImmutableSet.of("booleanValue", "charValue", "byteValue", "shortValue", "intValue", "floatValue", "longValue", "doubleValue")
|
||||||
|
|
||||||
private fun isWrapperClassNameOrNumber(internalClassName: String) =
|
private val KCLASS_TO_JLCLASS = Type.getMethodDescriptor(AsmTypes.JAVA_CLASS_TYPE, AsmTypes.K_CLASS_TYPE)
|
||||||
isWrapperClassName(internalClassName) || internalClassName == Type.getInternalName(Number::class.java)
|
private val JLCLASS_TO_KCLASS = Type.getMethodDescriptor(AsmTypes.K_CLASS_TYPE, AsmTypes.JAVA_CLASS_TYPE)
|
||||||
|
|
||||||
private fun isWrapperClassName(internalClassName: String) =
|
fun AbstractInsnNode.isUnboxing() =
|
||||||
JvmPrimitiveType.isWrapperClassName(buildFqNameByInternal(internalClassName))
|
isPrimitiveUnboxing() || isJavaLangClassUnboxing()
|
||||||
|
|
||||||
private fun buildFqNameByInternal(internalClassName: String) =
|
fun AbstractInsnNode.isBoxing() =
|
||||||
FqName(Type.getObjectType(internalClassName).className)
|
isPrimitiveBoxing() || isJavaLangClassBoxing()
|
||||||
|
|
||||||
private fun AbstractInsnNode.isUnboxing() =
|
fun AbstractInsnNode.isPrimitiveUnboxing() =
|
||||||
isPrimitiveUnboxing() || isJavaLangClassUnboxing()
|
isMethodInsnWith(Opcodes.INVOKEVIRTUAL) {
|
||||||
|
isWrapperClassNameOrNumber(owner) && isUnboxingMethodName(name)
|
||||||
private inline fun AbstractInsnNode.isMethodInsnWith(opcode: Int, condition: MethodInsnNode.() -> Boolean): Boolean =
|
|
||||||
if (this.opcode == opcode && this is MethodInsnNode)
|
|
||||||
this.condition()
|
|
||||||
else
|
|
||||||
false
|
|
||||||
|
|
||||||
private fun AbstractInsnNode.isPrimitiveUnboxing() =
|
|
||||||
isMethodInsnWith(Opcodes.INVOKEVIRTUAL) {
|
|
||||||
isWrapperClassNameOrNumber(owner) && isUnboxingMethodName(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun AbstractInsnNode.isJavaLangClassUnboxing() =
|
|
||||||
isMethodInsnWith(Opcodes.INVOKESTATIC) {
|
|
||||||
owner == "kotlin/jvm/JvmClassMappingKt" &&
|
|
||||||
name == "getJavaClass" &&
|
|
||||||
desc == KCLASS_TO_JLCLASS
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isUnboxingMethodName(name: String) =
|
|
||||||
UNBOXING_METHOD_NAMES.contains(name)
|
|
||||||
|
|
||||||
private fun AbstractInsnNode.isBoxing() =
|
|
||||||
this.isPrimitiveBoxing() || this.isJavaLangClassBoxing()
|
|
||||||
|
|
||||||
private fun AbstractInsnNode.isPrimitiveBoxing() =
|
|
||||||
isMethodInsnWith(Opcodes.INVOKESTATIC) {
|
|
||||||
isWrapperClassName(owner) &&
|
|
||||||
name == "valueOf" &&
|
|
||||||
isBoxingMethodDescriptor()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun MethodInsnNode.isBoxingMethodDescriptor(): Boolean {
|
|
||||||
val ownerType = Type.getObjectType(owner)
|
|
||||||
return desc == Type.getMethodDescriptor(ownerType, AsmUtil.unboxType(ownerType))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun AbstractInsnNode.isJavaLangClassBoxing() =
|
private fun AbstractInsnNode.isJavaLangClassUnboxing() =
|
||||||
isMethodInsnWith(Opcodes.INVOKESTATIC) {
|
isMethodInsnWith(Opcodes.INVOKESTATIC) {
|
||||||
owner == AsmTypes.REFLECTION &&
|
owner == "kotlin/jvm/JvmClassMappingKt" &&
|
||||||
name == "getOrCreateKotlinClass" &&
|
name == "getJavaClass" &&
|
||||||
desc == JLCLASS_TO_KCLASS
|
desc == KCLASS_TO_JLCLASS
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun AbstractInsnNode.isNextMethodCallOfProgressionIterator(values: List<BasicValue>) =
|
inline fun AbstractInsnNode.isMethodInsnWith(opcode: Int, condition: MethodInsnNode.() -> Boolean): Boolean =
|
||||||
values[0] is ProgressionIteratorBasicValue &&
|
this.opcode == opcode && this is MethodInsnNode && this.condition()
|
||||||
isMethodInsnWith(INVOKEINTERFACE) {
|
|
||||||
name == "next"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun AbstractInsnNode.isIteratorMethodCallOfProgression(values: List<BasicValue>) =
|
private fun isWrapperClassNameOrNumber(internalClassName: String) =
|
||||||
isMethodInsnWith(INVOKEINTERFACE) {
|
isWrapperClassName(internalClassName) || internalClassName == Type.getInternalName(Number::class.java)
|
||||||
val firstArgType = values[0].type
|
|
||||||
firstArgType != null &&
|
|
||||||
isProgressionClass(firstArgType.internalName) &&
|
|
||||||
name == "iterator"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isProgressionClass(internalClassName: String) =
|
private fun isWrapperClassName(internalClassName: String) =
|
||||||
RangeCodegenUtil.isRangeOrProgression(buildFqNameByInternal(internalClassName))
|
JvmPrimitiveType.isWrapperClassName(buildFqNameByInternal(internalClassName))
|
||||||
|
|
||||||
private fun getValuesTypeOfProgressionClass(progressionClassInternalName: String) =
|
|
||||||
RangeCodegenUtil.getPrimitiveRangeOrProgressionElementType(buildFqNameByInternal(progressionClassInternalName))?.let {
|
private fun buildFqNameByInternal(internalClassName: String) =
|
||||||
type ->
|
FqName(Type.getObjectType(internalClassName).className)
|
||||||
type.typeName.asString()
|
|
||||||
} ?: error("type should be not null")
|
private fun isUnboxingMethodName(name: String) =
|
||||||
}
|
UNBOXING_METHOD_NAMES.contains(name)
|
||||||
|
|
||||||
|
fun AbstractInsnNode.isPrimitiveBoxing() =
|
||||||
|
isMethodInsnWith(Opcodes.INVOKESTATIC) {
|
||||||
|
isWrapperClassName(owner) &&
|
||||||
|
name == "valueOf" &&
|
||||||
|
isBoxingMethodDescriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MethodInsnNode.isBoxingMethodDescriptor(): Boolean {
|
||||||
|
val ownerType = Type.getObjectType(owner)
|
||||||
|
return desc == Type.getMethodDescriptor(ownerType, AsmUtil.unboxType(ownerType))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun AbstractInsnNode.isJavaLangClassBoxing() =
|
||||||
|
isMethodInsnWith(Opcodes.INVOKESTATIC) {
|
||||||
|
owner == AsmTypes.REFLECTION &&
|
||||||
|
name == "getOrCreateKotlinClass" &&
|
||||||
|
desc == JLCLASS_TO_KCLASS
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun AbstractInsnNode.isNextMethodCallOfProgressionIterator(values: List<BasicValue>) =
|
||||||
|
values[0] is ProgressionIteratorBasicValue &&
|
||||||
|
isMethodInsnWith(Opcodes.INVOKEINTERFACE) {
|
||||||
|
name == "next"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun AbstractInsnNode.isIteratorMethodCallOfProgression(values: List<BasicValue>) =
|
||||||
|
isMethodInsnWith(Opcodes.INVOKEINTERFACE) {
|
||||||
|
val firstArgType = values[0].type
|
||||||
|
firstArgType != null &&
|
||||||
|
isProgressionClass(firstArgType.internalName) &&
|
||||||
|
name == "iterator"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isProgressionClass(internalClassName: String) =
|
||||||
|
RangeCodegenUtil.isRangeOrProgression(buildFqNameByInternal(internalClassName))
|
||||||
|
|
||||||
|
private fun getValuesTypeOfProgressionClass(progressionClassInternalName: String) =
|
||||||
|
RangeCodegenUtil.getPrimitiveRangeOrProgressionElementType(buildFqNameByInternal(progressionClassInternalName))?.let {
|
||||||
|
type ->
|
||||||
|
type.typeName.asString()
|
||||||
|
} ?: error("type should be not null")
|
||||||
+228
@@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2016 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.kotlin.codegen.optimization.common.InsnSequence
|
||||||
|
import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful
|
||||||
|
import org.jetbrains.kotlin.codegen.optimization.fixStack.peek
|
||||||
|
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
|
||||||
|
import org.jetbrains.kotlin.codegen.optimization.removeNodeGetNext
|
||||||
|
import org.jetbrains.kotlin.codegen.optimization.replaceNodeGetNext
|
||||||
|
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.Analyzer
|
||||||
|
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame
|
||||||
|
import org.jetbrains.org.objectweb.asm.tree.analysis.SourceInterpreter
|
||||||
|
import org.jetbrains.org.objectweb.asm.tree.analysis.SourceValue
|
||||||
|
import org.jetbrains.org.objectweb.asm.util.Printer
|
||||||
|
|
||||||
|
class RedundantCoercionToUnitTransformer : MethodTransformer() {
|
||||||
|
override fun transform(internalClassName: String, methodNode: MethodNode) {
|
||||||
|
Transformer(methodNode).transform()
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Transformer(val methodNode: MethodNode) {
|
||||||
|
private val insnList = methodNode.instructions
|
||||||
|
|
||||||
|
private val frames: Array<Frame<SourceValue>?> = Analyzer<SourceValue>(SourceInterpreter()).analyze("fake", methodNode)
|
||||||
|
private val insns = insnList.toArray()
|
||||||
|
|
||||||
|
private val dontTouchInsns = hashSetOf<AbstractInsnNode>()
|
||||||
|
private val transformations = hashMapOf<AbstractInsnNode, () -> Unit>()
|
||||||
|
private val removableNops = hashSetOf<InsnNode>()
|
||||||
|
|
||||||
|
fun transform() {
|
||||||
|
computeDontTouchInsns()
|
||||||
|
computeTransformations()
|
||||||
|
transformations.values.forEach { it() }
|
||||||
|
postprocessNops()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun computeDontTouchInsns() {
|
||||||
|
for (i in 0..insns.lastIndex) {
|
||||||
|
val frame = frames[i] ?: continue
|
||||||
|
val insn = insns[i]
|
||||||
|
|
||||||
|
when (insn.opcode) {
|
||||||
|
Opcodes.DUP ->
|
||||||
|
dontTouchWordsOnTop(i, frame, 1)
|
||||||
|
Opcodes.DUP_X1 ->
|
||||||
|
dontTouchWordsOnTop(i, frame, 2)
|
||||||
|
Opcodes.DUP_X2 ->
|
||||||
|
dontTouchWordsOnTop(i, frame, 3)
|
||||||
|
Opcodes.DUP2 ->
|
||||||
|
dontTouchWordsOnTop(i, frame, 2)
|
||||||
|
Opcodes.DUP2_X1 ->
|
||||||
|
dontTouchWordsOnTop(i, frame, 3)
|
||||||
|
Opcodes.DUP2_X2 ->
|
||||||
|
dontTouchWordsOnTop(i, frame, 4)
|
||||||
|
Opcodes.SWAP ->
|
||||||
|
dontTouchWordsOnTop(i, frame, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dontTouchWordsOnTop(at: Int, frame: Frame<SourceValue>, expectedWords: Int) {
|
||||||
|
var words = 0
|
||||||
|
var offset = 0
|
||||||
|
while (words < expectedWords) {
|
||||||
|
val value = frame.peek(offset) ?: throwIllegalStackInsn(at)
|
||||||
|
offset++
|
||||||
|
words += value.size
|
||||||
|
dontTouchInsns.addAll(value.insns)
|
||||||
|
}
|
||||||
|
if (words != expectedWords) throwIllegalStackInsn(at)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun computeTransformations() {
|
||||||
|
transformations.clear()
|
||||||
|
|
||||||
|
for (i in 0..insns.lastIndex) {
|
||||||
|
if (frames[i] == null) continue
|
||||||
|
val insn = insns[i]
|
||||||
|
|
||||||
|
if (insn.opcode == Opcodes.POP) {
|
||||||
|
propagatePopBackwards(insn, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun propagatePopBackwards(insn: AbstractInsnNode, poppedValueSize: Int) {
|
||||||
|
if (transformations.containsKey(insn)) return
|
||||||
|
|
||||||
|
when {
|
||||||
|
insn.opcode == Opcodes.POP -> {
|
||||||
|
val inputTop = getInputTop(insn)
|
||||||
|
val sources = inputTop.insns
|
||||||
|
if (sources.all { !isDontTouch(it) } && sources.any { isTransformablePopOperand(it) }) {
|
||||||
|
transformations[insn] = replaceWithNopTransformation(insn)
|
||||||
|
sources.forEach { propagatePopBackwards(it, inputTop.size) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
insn.opcode == Opcodes.CHECKCAST -> {
|
||||||
|
val inputTop = getInputTop(insn)
|
||||||
|
val sources = inputTop.insns
|
||||||
|
val resultType = (insn as TypeInsnNode).desc
|
||||||
|
if (sources.all { !isDontTouch(it) } && sources.any { isTransformableCheckcastOperand(it, resultType) }) {
|
||||||
|
transformations[insn] = replaceWithNopTransformation(insn)
|
||||||
|
sources.forEach { propagatePopBackwards(it, inputTop.size) }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
transformations[insn] = insertPopAfterTransformation(insn, poppedValueSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
insn.isPrimitiveBoxing() -> {
|
||||||
|
val boxedValueSize = getInputTop(insn).size
|
||||||
|
transformations[insn] = replaceWithPopTransformation(insn, boxedValueSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
insn.isUnitOrNull() -> {
|
||||||
|
transformations[insn] = replaceWithNopTransformation(insn)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
transformations[insn] = insertPopAfterTransformation(insn, poppedValueSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun postprocessNops() {
|
||||||
|
var node: AbstractInsnNode? = insnList.first
|
||||||
|
var hasRemovableNops = false
|
||||||
|
while (node != null) {
|
||||||
|
node = node.next
|
||||||
|
val begin = node ?: break
|
||||||
|
while (node != null && node !is LabelNode) {
|
||||||
|
if (node in removableNops) {
|
||||||
|
hasRemovableNops = true
|
||||||
|
}
|
||||||
|
node = node.next
|
||||||
|
}
|
||||||
|
val end = node
|
||||||
|
if (hasRemovableNops) {
|
||||||
|
removeUnneededNopsInRange(begin, end)
|
||||||
|
}
|
||||||
|
hasRemovableNops = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeUnneededNopsInRange(begin: AbstractInsnNode, end: AbstractInsnNode?) {
|
||||||
|
var node: AbstractInsnNode? = begin
|
||||||
|
var keepNop = true
|
||||||
|
while (node != null && node != end) {
|
||||||
|
if (node in removableNops && !keepNop) {
|
||||||
|
node = insnList.removeNodeGetNext(node)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (node.isMeaningful) keepNop = false
|
||||||
|
node = node.next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun replaceWithPopTransformation(insn: AbstractInsnNode, size: Int): () -> Unit =
|
||||||
|
{ insnList.replaceNodeGetNext(insn, createPopInsn(size)) }
|
||||||
|
|
||||||
|
private fun insertPopAfterTransformation(insn: AbstractInsnNode, size: Int) =
|
||||||
|
{ insnList.insert(insn, createPopInsn(size)) }
|
||||||
|
|
||||||
|
private fun replaceWithNopTransformation(insn: AbstractInsnNode): () -> Unit =
|
||||||
|
{ insnList.replaceNodeGetNext(insn, createRemovableNopInsn()) }
|
||||||
|
|
||||||
|
private fun createPopInsn(size: Int) =
|
||||||
|
when (size) {
|
||||||
|
1 -> InsnNode(Opcodes.POP)
|
||||||
|
2 -> InsnNode(Opcodes.POP2)
|
||||||
|
else -> throw AssertionError("Unexpected popped value size: $size")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createRemovableNopInsn() =
|
||||||
|
InsnNode(Opcodes.NOP).apply { removableNops.add(this) }
|
||||||
|
|
||||||
|
private fun getInputTop(insn: AbstractInsnNode): SourceValue {
|
||||||
|
val i = insnList.indexOf(insn)
|
||||||
|
val frame = frames[i] ?: throw AssertionError("Unexpected dead instruction #$i")
|
||||||
|
return frame.top() ?: throw AssertionError("Instruction #$i has empty stack on input")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isTransformableCheckcastOperand(it: AbstractInsnNode, resultType: String) =
|
||||||
|
it.isPrimitiveBoxing() && (it as MethodInsnNode).owner == resultType
|
||||||
|
|
||||||
|
private fun isTransformablePopOperand(insn: AbstractInsnNode) =
|
||||||
|
insn.opcode == Opcodes.CHECKCAST || insn.isPrimitiveBoxing() || insn.isUnitOrNull()
|
||||||
|
|
||||||
|
private fun isDontTouch(insn: AbstractInsnNode) =
|
||||||
|
insn in dontTouchInsns
|
||||||
|
|
||||||
|
private fun throwIllegalStackInsn(i: Int): Nothing =
|
||||||
|
throw AssertionError("#$i: illegal use of ${Printer.OPCODES[insns[i].opcode]}, input stack: ${formatInputStack(frames[i])}")
|
||||||
|
|
||||||
|
private fun formatInputStack(frame: Frame<SourceValue>?): String =
|
||||||
|
if (frame == null)
|
||||||
|
"unknown (dead code)"
|
||||||
|
else
|
||||||
|
(0..frame.stackSize - 1).map { frame.getStack(it).size }.joinToString(prefix = "[", postfix = "]")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun AbstractInsnNode.isUnitOrNull() =
|
||||||
|
opcode == Opcodes.ACONST_NULL ||
|
||||||
|
opcode == Opcodes.GETSTATIC && this is FieldInsnNode && owner == "kotlin/Unit" && name == "INSTANCE"
|
||||||
+5
-7
@@ -41,13 +41,11 @@ inline fun InsnList.forEachInlineMarker(block: (String, MethodInsnNode) -> Unit)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <V : Value> Frame<V>.top(): V? {
|
fun <V : Value> Frame<V>.top(): V? =
|
||||||
val stackSize = stackSize
|
peek(0)
|
||||||
if (stackSize == 0)
|
|
||||||
return null
|
fun <V : Value> Frame<V>.peek(offset: Int): V? =
|
||||||
else
|
if (stackSize >= offset) getStack(stackSize - offset - 1) else null
|
||||||
return getStack(stackSize - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun MethodNode.updateMaxLocals(newMaxLocals: Int) {
|
fun MethodNode.updateMaxLocals(newMaxLocals: Int) {
|
||||||
maxLocals = Math.max(maxLocals, newMaxLocals)
|
maxLocals = Math.max(maxLocals, newMaxLocals)
|
||||||
|
|||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
fun test() {
|
||||||
|
val a = inlineFunInt { 1 }
|
||||||
|
val b = simpleFunInt { 1 }
|
||||||
|
val c = inlineFunVoid { val aa = 1 }
|
||||||
|
val d = simpleFunVoid { val aa = 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun inlineFunInt(f: () -> Int): Int {
|
||||||
|
val a = 1
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun inlineFunVoid(f: () -> Unit): Unit {
|
||||||
|
val a = 1
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun simpleFunInt(f: () -> Int): Int {
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun simpleFunVoid(f: () -> Unit): Unit {
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3 NOP
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
fun test() = Unit
|
||||||
|
|
||||||
|
// 0 GETSTATIC
|
||||||
|
// 0 POP
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
interface A {
|
||||||
|
fun foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test(x: A?) {
|
||||||
|
x?.foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1 POP
|
||||||
|
// 0 ACONST_NULL
|
||||||
Vendored
+10
@@ -0,0 +1,10 @@
|
|||||||
|
interface A {
|
||||||
|
fun foo(): Any
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test(x: A?) {
|
||||||
|
x?.foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2 POP
|
||||||
|
// 0 ACONST_NULL
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
fun test(ss: List<String?>) {
|
||||||
|
val shortStrings = hashSetOf<String>()
|
||||||
|
val longStrings = hashSetOf<String>()
|
||||||
|
for (s in ss) {
|
||||||
|
s?.let {
|
||||||
|
if (s.length < 4) {
|
||||||
|
shortStrings.add(s)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
longStrings.add(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2 POP
|
||||||
|
// 0 INVOKESTATIC java/lang/Boolean\.valueOf
|
||||||
|
// 0 CHECKCAST java/lang/Boolean
|
||||||
|
// 0 ACONST_NULL
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
import java.io.*
|
||||||
|
|
||||||
|
fun test(r: Reader) {
|
||||||
|
val ss = hashSetOf<String>()
|
||||||
|
r.useLines { it.forEach { ss.add(it) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2 POP
|
||||||
@@ -575,6 +575,51 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("compiler/testData/codegen/bytecodeText/coercionToUnitOptimization")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class CoercionToUnitOptimization extends AbstractBytecodeTextTest {
|
||||||
|
public void testAllFilesPresentInCoercionToUnitOptimization() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/coercionToUnitOptimization"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("nopInlineFuns.kt")
|
||||||
|
public void testNopInlineFuns() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/nopInlineFuns.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("returnsUnit.kt")
|
||||||
|
public void testReturnsUnit() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/returnsUnit.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("safeCall.kt")
|
||||||
|
public void testSafeCall() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/safeCall.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("safeCallWithReturnValue.kt")
|
||||||
|
public void testSafeCallWithReturnValue() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/safeCallWithReturnValue.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("safeLet.kt")
|
||||||
|
public void testSafeLet() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/safeLet.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("tryInlined.kt")
|
||||||
|
public void testTryInlined() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/coercionToUnitOptimization/tryInlined.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("compiler/testData/codegen/bytecodeText/conditions")
|
@TestMetadata("compiler/testData/codegen/bytecodeText/conditions")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user