From a1ae40a617e4a9c8758c472efde9f47cf625dd58 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Fri, 19 May 2017 15:36:24 +0300 Subject: [PATCH] RedundantBoxingMethodTransformer: convert to Kotlin & cleanup --- .../RedundantBoxingMethodTransformer.kt | 717 +++++++++--------- 1 file changed, 343 insertions(+), 374 deletions(-) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantBoxingMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantBoxingMethodTransformer.kt index fc7e0057739..b84af353a2d 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantBoxingMethodTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantBoxingMethodTransformer.kt @@ -14,503 +14,472 @@ * limitations under the License. */ -package org.jetbrains.kotlin.codegen.optimization.boxing; +package org.jetbrains.kotlin.codegen.optimization.boxing -import com.intellij.openapi.util.Pair; -import kotlin.collections.CollectionsKt; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil; -import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue; -import org.jetbrains.kotlin.codegen.optimization.common.UtilKt; -import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer; -import org.jetbrains.org.objectweb.asm.Label; -import org.jetbrains.org.objectweb.asm.Opcodes; -import org.jetbrains.org.objectweb.asm.Type; -import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter; -import org.jetbrains.org.objectweb.asm.tree.*; -import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue; -import org.jetbrains.org.objectweb.asm.tree.analysis.Frame; +import com.intellij.openapi.util.Pair +import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil +import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue +import org.jetbrains.kotlin.codegen.optimization.common.* +import org.jetbrains.kotlin.codegen.optimization.fixStack.peek +import org.jetbrains.kotlin.codegen.optimization.fixStack.top +import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer +import org.jetbrains.org.objectweb.asm.Label +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.Type +import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter +import org.jetbrains.org.objectweb.asm.tree.* +import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue +import org.jetbrains.org.objectweb.asm.tree.analysis.Frame -import java.util.*; +import java.util.* -public class RedundantBoxingMethodTransformer extends MethodTransformer { +class RedundantBoxingMethodTransformer : MethodTransformer() { - @Override - public void transform(@NotNull String internalClassName, @NotNull MethodNode node) { - RedundantBoxingInterpreter interpreter = new RedundantBoxingInterpreter(node.instructions); - Frame[] frames = analyze(internalClassName, node, interpreter); + override fun transform(internalClassName: String, node: MethodNode) { + val interpreter = RedundantBoxingInterpreter(node.instructions) + val frames = MethodTransformer.analyze(internalClassName, node, interpreter) - interpretPopInstructionsForBoxedValues(interpreter, node, frames); + interpretPopInstructionsForBoxedValues(interpreter, node, frames) - RedundantBoxedValuesCollection valuesToOptimize = interpreter.getCandidatesBoxedValues(); + val valuesToOptimize = interpreter.candidatesBoxedValues - if (!valuesToOptimize.isEmpty()) { + if (!valuesToOptimize.isEmpty) { // has side effect on valuesToOptimize and frames, containing BoxedBasicValues that are unsafe to remove - removeValuesClashingWithVariables(valuesToOptimize, node, frames); + removeValuesClashingWithVariables(valuesToOptimize, node, frames) - adaptLocalVariableTableForBoxedValues(node, frames); + adaptLocalVariableTableForBoxedValues(node, frames) - UtilKt.remapLocalVariables(node, buildVariablesRemapping(valuesToOptimize, node)); + node.remapLocalVariables(buildVariablesRemapping(valuesToOptimize, node)) - adaptInstructionsForBoxedValues(node, valuesToOptimize); + adaptInstructionsForBoxedValues(node, valuesToOptimize) } } - private static void interpretPopInstructionsForBoxedValues( - @NotNull RedundantBoxingInterpreter interpreter, - @NotNull MethodNode node, - @NotNull Frame[] frames + private fun interpretPopInstructionsForBoxedValues( + interpreter: RedundantBoxingInterpreter, + node: MethodNode, + frames: Array?> ) { - for (int i = 0; i < node.instructions.size(); i++) { - AbstractInsnNode insn = node.instructions.get(i); - if ((insn.getOpcode() != Opcodes.POP && insn.getOpcode() != Opcodes.POP2) || frames[i] == null) { - continue; + for (i in 0..node.instructions.size() - 1) { + val insn = node.instructions.get(i) + val frame = frames[i] + if (insn.opcode != Opcodes.POP && insn.opcode != Opcodes.POP2 || frame == null) { + continue } - BasicValue top = frames[i].getStack(frames[i].getStackSize() - 1); - interpreter.processPopInstruction(insn, top); + val top = frame.top()!! + interpreter.processPopInstruction(insn, top) - if (top.getSize() == 1 && insn.getOpcode() == Opcodes.POP2) { - interpreter.processPopInstruction(insn, frames[i].getStack(frames[i].getStackSize() - 2)); + if (top.size == 1 && insn.opcode == Opcodes.POP2) { + interpreter.processPopInstruction(insn, frame.peek(1)!!) } } } - private static void removeValuesClashingWithVariables( - @NotNull RedundantBoxedValuesCollection values, - @NotNull MethodNode node, - @NotNull Frame[] frames + private fun removeValuesClashingWithVariables( + values: RedundantBoxedValuesCollection, + node: MethodNode, + frames: Array> ) { while (removeValuesClashingWithVariablesPass(values, node, frames)) { // do nothing } } - private static boolean removeValuesClashingWithVariablesPass( - @NotNull RedundantBoxedValuesCollection values, - @NotNull MethodNode node, - @NotNull Frame[] frames - ) { - boolean needToRepeat = false; + private fun removeValuesClashingWithVariablesPass( + values: RedundantBoxedValuesCollection, + node: MethodNode, + frames: Array?> + ): Boolean { + var needToRepeat = false - for (LocalVariableNode localVariableNode : node.localVariables) { - if (Type.getType(localVariableNode.desc).getSort() != Type.OBJECT) { - continue; + for (localVariableNode in node.localVariables) { + if (Type.getType(localVariableNode.desc).sort != Type.OBJECT) { + continue } - List variableValues = getValuesStoredOrLoadedToVariable(localVariableNode, node, frames); + val variableValues = getValuesStoredOrLoadedToVariable(localVariableNode, node, frames) - Collection boxed = CollectionsKt.filter(variableValues, value -> value instanceof BoxedBasicValue); + val boxed = variableValues.filterIsInstance() - if (boxed.isEmpty()) continue; + if (boxed.isEmpty()) continue - BoxedValueDescriptor firstBoxed = ((BoxedBasicValue) boxed.iterator().next()).getDescriptor(); - if (isUnsafeToRemoveBoxingForConnectedValues(variableValues, firstBoxed.getUnboxedType())) { - for (BasicValue value : variableValues) { - if (!(value instanceof BoxedBasicValue)) continue; - - BoxedValueDescriptor descriptor = ((BoxedBasicValue) value).getDescriptor(); - if (descriptor.isSafeToRemove()) { - values.remove(descriptor); - needToRepeat = true; + val firstBoxed = boxed.iterator().next().descriptor + if (isUnsafeToRemoveBoxingForConnectedValues(variableValues, firstBoxed.unboxedType)) { + for (value in boxed) { + val descriptor = value.descriptor + if (descriptor.isSafeToRemove) { + values.remove(descriptor) + needToRepeat = true } } } } - return needToRepeat; + return needToRepeat } - private static boolean isUnsafeToRemoveBoxingForConnectedValues(List usedValues, Type unboxedType) { - return CollectionsKt.any(usedValues, input -> { - if (input == StrictBasicValue.UNINITIALIZED_VALUE) return false; - if (!(input instanceof BoxedBasicValue)) return true; + private fun isUnsafeToRemoveBoxingForConnectedValues(usedValues: List, unboxedType: Type): Boolean = + usedValues.any { input -> + if (input === StrictBasicValue.UNINITIALIZED_VALUE) return@any false + if (input !is BoxedBasicValue) return@any true - BoxedValueDescriptor descriptor = ((BoxedBasicValue) input).getDescriptor(); - return !descriptor.isSafeToRemove() || - !(descriptor.getUnboxedType().equals(unboxedType)); - }); - } - - private static void adaptLocalVariableTableForBoxedValues(@NotNull MethodNode node, @NotNull Frame[] frames) { - for (LocalVariableNode localVariableNode : node.localVariables) { - if (Type.getType(localVariableNode.desc).getSort() != Type.OBJECT) { - continue; + val descriptor = input.descriptor + !descriptor.isSafeToRemove || descriptor.unboxedType != unboxedType } - for (BasicValue value : getValuesStoredOrLoadedToVariable(localVariableNode, node, frames)) { - if (!(value instanceof BoxedBasicValue)) continue; + private fun adaptLocalVariableTableForBoxedValues(node: MethodNode, frames: Array>) { + for (localVariableNode in node.localVariables) { + if (Type.getType(localVariableNode.desc).sort != Type.OBJECT) { + continue + } - BoxedValueDescriptor descriptor = ((BoxedBasicValue) value).getDescriptor(); - if (!descriptor.isSafeToRemove()) continue; - localVariableNode.desc = descriptor.getUnboxedType().getDescriptor(); + for (value in getValuesStoredOrLoadedToVariable(localVariableNode, node, frames)) { + if (value !is BoxedBasicValue) continue + + val descriptor = value.descriptor + if (!descriptor.isSafeToRemove) continue + localVariableNode.desc = descriptor.unboxedType.descriptor } } } - @NotNull - private static List getValuesStoredOrLoadedToVariable( - @NotNull LocalVariableNode localVariableNode, - @NotNull MethodNode node, - @NotNull Frame[] frames - ) { - List values = new ArrayList<>(); - InsnList insnList = node.instructions; - int from = insnList.indexOf(localVariableNode.start) + 1; - int to = insnList.indexOf(localVariableNode.end) - 1; + private fun getValuesStoredOrLoadedToVariable( + localVariableNode: LocalVariableNode, + node: MethodNode, + frames: Array?> + ): List { + val values = ArrayList() + val insnList = node.instructions + val localVariableStart = insnList.indexOf(localVariableNode.start) + val localVariableEnd = insnList.indexOf(localVariableNode.end) - Frame frameForFromInstr = frames[from]; - if (frameForFromInstr != null) { - BasicValue localVarValue = frameForFromInstr.getLocal(localVariableNode.index); - if (localVarValue != null) { - values.add(localVarValue); + frames[localVariableStart]?.let { frameForStartInsn -> + frameForStartInsn.getLocal(localVariableNode.index)?.let { localVarValue -> + values.add(localVarValue) } } - for (int i = from; i <= to; i++) { - if (i < 0 || i >= insnList.size()) continue; - - AbstractInsnNode insn = insnList.get(i); - if ((insn.getOpcode() == Opcodes.ASTORE || insn.getOpcode() == Opcodes.ALOAD) && - ((VarInsnNode) insn).var == localVariableNode.index) { - - if (frames[i] == null) { - //unreachable code - continue; - } - + for (i in localVariableStart until localVariableEnd) { + if (i < 0 || i >= insnList.size()) continue + val frame = frames[i] ?: continue + val insn = insnList[i] + if ((insn.opcode == Opcodes.ASTORE || insn.opcode == Opcodes.ALOAD) && + (insn as VarInsnNode).`var` == localVariableNode.index) { if (insn.getOpcode() == Opcodes.ASTORE) { - values.add(frames[i].getStack(frames[i].getStackSize() - 1)); + values.add(frame.top()!!) } else { - values.add(frames[i].getLocal(((VarInsnNode) insn).var)); + values.add(frame.getLocal(insn.`var`)) } } } - return values; + return values } - @NotNull - private static int[] buildVariablesRemapping(@NotNull RedundantBoxedValuesCollection values, @NotNull MethodNode node) { - Set doubleSizedVars = new HashSet<>(); - for (BoxedValueDescriptor valueDescriptor : values) { + private fun buildVariablesRemapping(values: RedundantBoxedValuesCollection, node: MethodNode): IntArray { + val doubleSizedVars = HashSet() + for (valueDescriptor in values) { if (valueDescriptor.isDoubleSize()) { - doubleSizedVars.addAll(valueDescriptor.getVariablesIndexes()); + doubleSizedVars.addAll(valueDescriptor.getVariablesIndexes()) } } - node.maxLocals += doubleSizedVars.size(); - int[] remapping = new int[node.maxLocals]; - for (int i = 0; i < remapping.length; i++) { - remapping[i] = i; + node.maxLocals += doubleSizedVars.size + val remapping = IntArray(node.maxLocals) + for (i in remapping.indices) { + remapping[i] = i } - for (int varIndex : doubleSizedVars) { - for (int i = varIndex + 1; i < remapping.length; i++) { - remapping[i]++; + for (varIndex in doubleSizedVars) { + for (i in varIndex + 1..remapping.size - 1) { + remapping[i]++ } } - return remapping; + return remapping } - private static void adaptInstructionsForBoxedValues( - @NotNull MethodNode node, - @NotNull RedundantBoxedValuesCollection values + private fun adaptInstructionsForBoxedValues( + node: MethodNode, + values: RedundantBoxedValuesCollection ) { - for (BoxedValueDescriptor value : values) { - adaptInstructionsForBoxedValue(node, value); + for (value in values) { + adaptInstructionsForBoxedValue(node, value) } } - private static void adaptInstructionsForBoxedValue(@NotNull MethodNode node, @NotNull BoxedValueDescriptor value) { - adaptBoxingInstruction(node, value); + private fun adaptInstructionsForBoxedValue(node: MethodNode, value: BoxedValueDescriptor) { + adaptBoxingInstruction(node, value) - for (Pair cast : value.getUnboxingWithCastInsns()) { - adaptCastInstruction(node, value, cast); + for (cast in value.getUnboxingWithCastInsns()) { + adaptCastInstruction(node, value, cast) } - for (AbstractInsnNode insn : value.getAssociatedInsns()) { - adaptInstruction(node, insn, value); + for (insn in value.getAssociatedInsns()) { + adaptInstruction(node, insn, value) } } - private static void adaptBoxingInstruction(@NotNull MethodNode node, @NotNull BoxedValueDescriptor value) { + private fun adaptBoxingInstruction(node: MethodNode, value: BoxedValueDescriptor) { if (!value.isFromProgressionIterator()) { - node.instructions.remove(value.getBoxingInsn()); + node.instructions.remove(value.boxingInsn) } else { - ProgressionIteratorBasicValue iterator = value.getProgressionIterator(); - assert iterator != null : "iterator should not be null because isFromProgressionIterator returns true"; + val iterator = value.progressionIterator ?: error("iterator should not be null because isFromProgressionIterator returns true") //add checkcast to kotlin/Iterator before next() call - node.instructions.insertBefore( - value.getBoxingInsn(), - new TypeInsnNode(Opcodes.CHECKCAST, iterator.getType().getInternalName()) - ); + node.instructions.insertBefore(value.boxingInsn, TypeInsnNode(Opcodes.CHECKCAST, iterator.type.internalName)) //invoke concrete method (kotlin/iterator.next()) node.instructions.set( - value.getBoxingInsn(), - new MethodInsnNode( + value.boxingInsn, + MethodInsnNode( Opcodes.INVOKEVIRTUAL, - iterator.getType().getInternalName(), - iterator.getNextMethodName(), - iterator.getNextMethodDesc(), + iterator.type.internalName, iterator.nextMethodName, iterator.nextMethodDesc, false ) - ); + ) } } - private static void adaptCastInstruction( - @NotNull MethodNode node, - @NotNull BoxedValueDescriptor value, - @NotNull Pair castWithType + private fun adaptCastInstruction( + node: MethodNode, + value: BoxedValueDescriptor, + castWithType: Pair ) { - AbstractInsnNode castInsn = castWithType.getFirst(); - MethodNode castInsnsListener = new MethodNode(Opcodes.ASM5); - new InstructionAdapter(castInsnsListener).cast(value.getUnboxedType(), castWithType.getSecond()); + val castInsn = castWithType.getFirst() + val castInsnsListener = MethodNode(Opcodes.ASM5) + InstructionAdapter(castInsnsListener).cast(value.unboxedType, castWithType.getSecond()) - for (AbstractInsnNode insn : castInsnsListener.instructions.toArray()) { - node.instructions.insertBefore(castInsn, insn); + for (insn in castInsnsListener.instructions.toArray()) { + node.instructions.insertBefore(castInsn, insn) } - node.instructions.remove(castInsn); + node.instructions.remove(castInsn) } - private static void adaptInstruction( - @NotNull MethodNode node, @NotNull AbstractInsnNode insn, @NotNull BoxedValueDescriptor value + private fun adaptInstruction( + node: MethodNode, insn: AbstractInsnNode, value: BoxedValueDescriptor ) { - boolean isDoubleSize = value.isDoubleSize(); + val isDoubleSize = value.isDoubleSize() - switch (insn.getOpcode()) { - case Opcodes.POP: + when (insn.opcode) { + Opcodes.POP -> if (isDoubleSize) { - node.instructions.set( - insn, - new InsnNode(Opcodes.POP2) - ); + node.instructions.set(insn, InsnNode(Opcodes.POP2)) } - break; - case Opcodes.DUP: + + Opcodes.DUP -> if (isDoubleSize) { - node.instructions.set( - insn, - new InsnNode(Opcodes.DUP2) - ); + node.instructions.set(insn, InsnNode(Opcodes.DUP2)) } - break; - case Opcodes.ASTORE: - case Opcodes.ALOAD: - int intVarOpcode = insn.getOpcode() == Opcodes.ASTORE ? Opcodes.ISTORE : Opcodes.ILOAD; - node.instructions.set( - insn, - new VarInsnNode( - value.getUnboxedType().getOpcode(intVarOpcode), - ((VarInsnNode) insn).var - ) - ); - break; - case Opcodes.INSTANCEOF: + + Opcodes.ASTORE, Opcodes.ALOAD -> { + val storeOpcode = value.unboxedType.getOpcode(if (insn.opcode == Opcodes.ASTORE) Opcodes.ISTORE else Opcodes.ILOAD) + node.instructions.set(insn, VarInsnNode(storeOpcode, (insn as VarInsnNode).`var`)) + } + + Opcodes.INSTANCEOF -> { node.instructions.insertBefore( insn, - new InsnNode(isDoubleSize ? Opcodes.POP2 : Opcodes.POP) - ); - node.instructions.set(insn, new InsnNode(Opcodes.ICONST_1)); - break; - case Opcodes.INVOKESTATIC: - if (BoxingInterpreterKt.isAreEqualIntrinsic(insn)) { - adaptAreEqualIntrinsic(node, insn, value); - break; + InsnNode(if (isDoubleSize) Opcodes.POP2 else Opcodes.POP) + ) + node.instructions.set(insn, InsnNode(Opcodes.ICONST_1)) + } + + Opcodes.INVOKESTATIC -> { + when { + insn.isAreEqualIntrinsic() -> + adaptAreEqualIntrinsic(node, insn, value) + insn.isJavaLangComparableCompareTo() -> + adaptJavaLangComparableCompareTo(node, insn, value) + insn.isJavaLangClassBoxing() || + insn.isJavaLangClassUnboxing() -> + node.instructions.remove(insn) + else -> + throwCannotAdaptInstruction(insn) } - else if (BoxingInterpreterKt.isJavaLangClassBoxing(insn) || BoxingInterpreterKt.isJavaLangClassUnboxing(insn)) { - node.instructions.remove(insn); - break; + } + + Opcodes.INVOKEINTERFACE -> { + if (insn.isJavaLangComparableCompareTo()) { + adaptJavaLangComparableCompareTo(node, insn, value) } else { - throwCannotAdaptInstruction(insn); + throwCannotAdaptInstruction(insn) } - case Opcodes.INVOKEINTERFACE: - if (BoxingInterpreterKt.isJavaLangComparableCompareTo(insn)) { - adaptJavaLangComparableCompareTo(node, insn, value); - break; + } + + Opcodes.CHECKCAST, + Opcodes.INVOKEVIRTUAL -> + node.instructions.remove(insn) + + else -> + throwCannotAdaptInstruction(insn) + } + } + + private fun throwCannotAdaptInstruction(insn: AbstractInsnNode): Nothing = + throw AssertionError("Cannot adapt instruction: ${InlineCodegenUtil.getInsnText(insn)}") + + private fun adaptAreEqualIntrinsic( + node: MethodNode, + insn: AbstractInsnNode, + value: BoxedValueDescriptor + ) { + val unboxedType = value.unboxedType + + when (unboxedType.sort) { + Type.BOOLEAN, Type.BYTE, Type.SHORT, Type.INT, Type.CHAR -> + adaptAreEqualIntrinsicForInt(node, insn) + Type.LONG -> + adaptAreEqualIntrinsicForLong(node, insn) + Type.OBJECT -> + {} + else -> + throw AssertionError("Unexpected unboxed type kind: $unboxedType") + } + } + + private fun adaptAreEqualIntrinsicForInt(node: MethodNode, insn: AbstractInsnNode) { + node.instructions.run { + val next = insn.next + if (next != null && (next.opcode == Opcodes.IFEQ || next.opcode == Opcodes.IFNE)) { + fuseAreEqualWithBranch(node, insn, Opcodes.IF_ICMPNE, Opcodes.IF_ICMPEQ) + remove(insn) + remove(next) + } + else { + ifEqual1Else0(node, insn, Opcodes.IF_ICMPNE) + remove(insn) + } + } + } + + private fun adaptAreEqualIntrinsicForLong(node: MethodNode, insn: AbstractInsnNode) { + node.instructions.run { + insertBefore(insn, InsnNode(Opcodes.LCMP)) + val next = insn.next + if (next != null && (next.opcode == Opcodes.IFEQ || next.opcode == Opcodes.IFNE)) { + fuseAreEqualWithBranch(node, insn, Opcodes.IFNE, Opcodes.IFEQ) + remove(insn) + remove(next) + } + else { + ifEqual1Else0(node, insn, Opcodes.IFNE) + remove(insn) + } + } + } + + private fun fuseAreEqualWithBranch( + node: MethodNode, + insn: AbstractInsnNode, + ifEqualOpcode: Int, + ifNotEqualOpcode: Int + ) { + node.instructions.run { + val next = insn.next + assert(next is JumpInsnNode) { "JumpInsnNode expected: $next" } + val nextLabel = (next as JumpInsnNode).label + when { + next.getOpcode() == Opcodes.IFEQ -> + insertBefore(insn, JumpInsnNode(ifEqualOpcode, nextLabel)) + next.getOpcode() == Opcodes.IFNE -> + insertBefore(insn, JumpInsnNode(ifNotEqualOpcode, nextLabel)) + else -> + throw AssertionError("IFEQ or IFNE expected: " + InlineCodegenUtil.getInsnOpcodeText(next)) + } + } + } + + private fun ifEqual1Else0(node: MethodNode, insn: AbstractInsnNode, ifneOpcode: Int) { + node.instructions.run { + val lNotEqual = LabelNode(Label()) + val lDone = LabelNode(Label()) + insertBefore(insn, JumpInsnNode(ifneOpcode, lNotEqual)) + insertBefore(insn, InsnNode(Opcodes.ICONST_1)) + insertBefore(insn, JumpInsnNode(Opcodes.GOTO, lDone)) + insertBefore(insn, lNotEqual) + insertBefore(insn, InsnNode(Opcodes.ICONST_0)) + insertBefore(insn, lDone) + } + } + + private fun adaptJavaLangComparableCompareTo( + node: MethodNode, + insn: AbstractInsnNode, + value: BoxedValueDescriptor + ) { + val unboxedType = value.unboxedType + + when (unboxedType.sort) { + Type.BOOLEAN, Type.BYTE, Type.SHORT, Type.INT, Type.CHAR -> + adaptJavaLangComparableCompareToForInt(node, insn) + Type.LONG -> + adaptJavaLangComparableCompareToForLong(node, insn) + Type.FLOAT -> + adaptJavaLangComparableCompareToForFloat(node, insn) + Type.DOUBLE -> + adaptJavaLangComparableCompareToForDouble(node, insn) + else -> + throw AssertionError("Unexpected unboxed type kind: $unboxedType") + } + } + + private fun adaptJavaLangComparableCompareToForInt(node: MethodNode, insn: AbstractInsnNode) { + node.instructions.run { + val next = insn.next + val next2 = next?.next + when { + next != null && next2 != null && + next.opcode == Opcodes.ICONST_0 && + next2.opcode >= Opcodes.IF_ICMPEQ && next2.opcode <= Opcodes.IF_ICMPLE -> { + // Fuse: compareTo + ICONST_0 + IF_ICMPxx -> IF_ICMPxx + remove(insn) + remove(next) } - else { - throwCannotAdaptInstruction(insn); + + next != null && + next.opcode >= Opcodes.IFEQ && next.opcode <= Opcodes.IFLE -> { + // Fuse: compareTo + IFxx -> IF_ICMPxx + val nextLabel = (next as JumpInsnNode).label + val ifCmpOpcode = next.opcode - Opcodes.IFEQ + Opcodes.IF_ICMPEQ + insertBefore(insn, JumpInsnNode(ifCmpOpcode, nextLabel)) + remove(insn) + remove(next) } - case Opcodes.CHECKCAST: - case Opcodes.INVOKEVIRTUAL: - // CHECKCAST or unboxing-method call - node.instructions.remove(insn); - break; - default: - throwCannotAdaptInstruction(insn); + + else -> { + // Can't fuse with branching instruction. + // Trick: convert I, I on stack to L, L and use LCMP. + // This is more compact than explicit branching. + // TODO Generate 'java.lang.Integer#compare(int, int)' in targets >= JVM 1.7 + + // Initial stack: I1 I2 + insertBefore(insn, InsnNode(Opcodes.SWAP)) // I2 I1 + insertBefore(insn, InsnNode(Opcodes.I2L)) // L2 I1 + insertBefore(insn, InsnNode(Opcodes.DUP2_X1)) // L2 I1 L2 + insertBefore(insn, InsnNode(Opcodes.POP2)) // I1 L2 + insertBefore(insn, InsnNode(Opcodes.I2L)) // L1 L2 + insertBefore(insn, InsnNode(Opcodes.LCMP)) // compare(L1, L2) + remove(insn) + } + } } } - private static void throwCannotAdaptInstruction(@NotNull AbstractInsnNode insn) { - throw new AssertionError("Cannot adapt instruction: " + InlineCodegenUtil.getInsnText(insn)); + private fun adaptJavaLangComparableCompareToForLong(node: MethodNode, insn: AbstractInsnNode) { + node.instructions.set(insn, InsnNode(Opcodes.LCMP)) } - private static void adaptAreEqualIntrinsic( - @NotNull MethodNode node, - @NotNull AbstractInsnNode insn, - @NotNull BoxedValueDescriptor value - ) { - Type unboxedType = value.getUnboxedType(); - - switch (unboxedType.getSort()) { - case Type.BOOLEAN: - case Type.BYTE: - case Type.SHORT: - case Type.INT: - case Type.CHAR: - adaptAreEqualIntrinsicForInt(node, insn); - break; - case Type.LONG: - adaptAreEqualIntrinsicForLong(node, insn); - break; - case Type.OBJECT: - break; - default: - throw new AssertionError("Unexpected unboxed type kind: " + unboxedType); - } + private fun adaptJavaLangComparableCompareToForFloat(node: MethodNode, insn: AbstractInsnNode) { + node.instructions.set(insn, MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Float", "compare", "(FF)I", false)) } - private static void adaptAreEqualIntrinsicForInt(@NotNull MethodNode node, @NotNull AbstractInsnNode insn) { - AbstractInsnNode next = insn.getNext(); - if (next != null && (next.getOpcode() == Opcodes.IFEQ || next.getOpcode() == Opcodes.IFNE)) { - fuseAreEqualWithBranch(node, insn, Opcodes.IF_ICMPNE, Opcodes.IF_ICMPEQ); - node.instructions.remove(insn); - node.instructions.remove(next); - } - else { - ifEqual1Else0(node, insn, Opcodes.IF_ICMPNE); - node.instructions.remove(insn); - } - } - - private static void adaptAreEqualIntrinsicForLong(@NotNull MethodNode node, @NotNull AbstractInsnNode insn) { - node.instructions.insertBefore(insn, new InsnNode(Opcodes.LCMP)); - AbstractInsnNode next = insn.getNext(); - if (next != null && (next.getOpcode() == Opcodes.IFEQ || next.getOpcode() == Opcodes.IFNE)) { - fuseAreEqualWithBranch(node, insn, Opcodes.IFNE, Opcodes.IFEQ); - node.instructions.remove(insn); - node.instructions.remove(next); - } - else { - ifEqual1Else0(node, insn, Opcodes.IFNE); - node.instructions.remove(insn); - } - } - - private static void fuseAreEqualWithBranch( - @NotNull MethodNode node, - @NotNull AbstractInsnNode insn, - int ifEqualOpcode, - int ifNotEqualOpcode - ) { - AbstractInsnNode next = insn.getNext(); - assert next instanceof JumpInsnNode : "JumpInsnNode expected: " + next; - LabelNode nextLabel = ((JumpInsnNode) next).label; - if (next.getOpcode() == Opcodes.IFEQ) { - node.instructions.insertBefore(insn, new JumpInsnNode(ifEqualOpcode, nextLabel)); - } - else if (next.getOpcode() == Opcodes.IFNE) { - node.instructions.insertBefore(insn, new JumpInsnNode(ifNotEqualOpcode, nextLabel)); - } - else { - throw new AssertionError("IFEQ or IFNE expected: " + InlineCodegenUtil.getInsnOpcodeText(next)); - } - } - - private static void ifEqual1Else0(@NotNull MethodNode node, @NotNull AbstractInsnNode insn, int ifneOpcode) { - LabelNode lNotEqual = new LabelNode(new Label()); - LabelNode lDone = new LabelNode(new Label()); - node.instructions.insertBefore(insn, new JumpInsnNode(ifneOpcode, lNotEqual)); - node.instructions.insertBefore(insn, new InsnNode(Opcodes.ICONST_1)); - node.instructions.insertBefore(insn, new JumpInsnNode(Opcodes.GOTO, lDone)); - node.instructions.insertBefore(insn, lNotEqual); - node.instructions.insertBefore(insn, new InsnNode(Opcodes.ICONST_0)); - node.instructions.insertBefore(insn, lDone); - } - - private static void adaptJavaLangComparableCompareTo( - @NotNull MethodNode node, - @NotNull AbstractInsnNode insn, - @NotNull BoxedValueDescriptor value - ) { - Type unboxedType = value.getUnboxedType(); - - switch (unboxedType.getSort()) { - case Type.BOOLEAN: - case Type.BYTE: - case Type.SHORT: - case Type.INT: - case Type.CHAR: - adaptJavaLangComparableCompareToForInt(node, insn); - break; - case Type.LONG: - adaptJavaLangComparableCompareToForLong(node, insn); - break; - case Type.FLOAT: - adaptJavaLangComparableCompareToForFloat(node, insn); - break; - case Type.DOUBLE: - adaptJavaLangComparableCompareToForDouble(node, insn); - break; - default: - throw new AssertionError("Unexpected unboxed type kind: " + unboxedType); - } - } - - private static void adaptJavaLangComparableCompareToForInt(@NotNull MethodNode node, @NotNull AbstractInsnNode insn) { - AbstractInsnNode next = insn.getNext(); - AbstractInsnNode next2 = next == null ? null : next.getNext(); - if (next != null && next2 != null && - next.getOpcode() == Opcodes.ICONST_0 && - next2.getOpcode() >= Opcodes.IF_ICMPEQ && next2.getOpcode() <= Opcodes.IF_ICMPLE) { - // Fuse: compareTo + ICONST_0 + IF_ICMPxx -> IF_ICMPxx - node.instructions.remove(insn); - node.instructions.remove(next); - } - else if (next != null && - next.getOpcode() >= Opcodes.IFEQ && next.getOpcode() <= Opcodes.IFLE) { - // Fuse: compareTo + IFxx -> IF_ICMPxx - LabelNode nextLabel = ((JumpInsnNode) next).label; - int ifCmpOpcode = next.getOpcode() - Opcodes.IFEQ + Opcodes.IF_ICMPEQ; - node.instructions.insertBefore(insn, new JumpInsnNode(ifCmpOpcode, nextLabel)); - node.instructions.remove(insn); - node.instructions.remove(next); - } - else { - // Can't fuse with branching instruction. - // Trick: convert I, I on stack to L, L and use LCMP. - // This is more compact than explicit branching. - // TODO Generate 'java.lang.Integer#compare(int, int)' in targets >= JVM 1.7 - - // Initial stack: I1 I2 - node.instructions.insertBefore(insn, new InsnNode(Opcodes.SWAP)); // I2 I1 - node.instructions.insertBefore(insn, new InsnNode(Opcodes.I2L)); // L2 I1 - node.instructions.insertBefore(insn, new InsnNode(Opcodes.DUP2_X1)); // L2 I1 L2 - node.instructions.insertBefore(insn, new InsnNode(Opcodes.POP2)); // I1 L2 - node.instructions.insertBefore(insn, new InsnNode(Opcodes.I2L)); // L1 L2 - node.instructions.insertBefore(insn, new InsnNode(Opcodes.LCMP)); // compare(L1, L2) - node.instructions.remove(insn); - } - } - - private static void adaptJavaLangComparableCompareToForLong(@NotNull MethodNode node, @NotNull AbstractInsnNode insn) { - node.instructions.set(insn, new InsnNode(Opcodes.LCMP)); - } - - private static void adaptJavaLangComparableCompareToForFloat(@NotNull MethodNode node, @NotNull AbstractInsnNode insn) { - node.instructions.set(insn, new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Float", "compare", "(FF)I", false)); - } - - private static void adaptJavaLangComparableCompareToForDouble(@NotNull MethodNode node, @NotNull AbstractInsnNode insn) { - node.instructions.set(insn, new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Double", "compare", "(DD)I", false)); + private fun adaptJavaLangComparableCompareToForDouble(node: MethodNode, insn: AbstractInsnNode) { + node.instructions.set(insn, MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Double", "compare", "(DD)I", false)) } }