RedundantBoxingMethodTransformer: convert to Kotlin & cleanup

This commit is contained in:
Dmitry Petrov
2017-05-19 15:36:24 +03:00
parent f1bb9a9839
commit a1ae40a617
@@ -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<BasicValue>[] 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<BasicValue>[] frames
private fun interpretPopInstructionsForBoxedValues(
interpreter: RedundantBoxingInterpreter,
node: MethodNode,
frames: Array<out Frame<BasicValue>?>
) {
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<BasicValue>[] frames
private fun removeValuesClashingWithVariables(
values: RedundantBoxedValuesCollection,
node: MethodNode,
frames: Array<Frame<BasicValue>>
) {
while (removeValuesClashingWithVariablesPass(values, node, frames)) {
// do nothing
}
}
private static boolean removeValuesClashingWithVariablesPass(
@NotNull RedundantBoxedValuesCollection values,
@NotNull MethodNode node,
@NotNull Frame<BasicValue>[] frames
) {
boolean needToRepeat = false;
private fun removeValuesClashingWithVariablesPass(
values: RedundantBoxedValuesCollection,
node: MethodNode,
frames: Array<out Frame<BasicValue>?>
): 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<BasicValue> variableValues = getValuesStoredOrLoadedToVariable(localVariableNode, node, frames);
val variableValues = getValuesStoredOrLoadedToVariable(localVariableNode, node, frames)
Collection<BasicValue> boxed = CollectionsKt.filter(variableValues, value -> value instanceof BoxedBasicValue);
val boxed = variableValues.filterIsInstance<BoxedBasicValue>()
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<BasicValue> 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<BasicValue>, 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<BasicValue>[] 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<Frame<BasicValue>>) {
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<BasicValue> getValuesStoredOrLoadedToVariable(
@NotNull LocalVariableNode localVariableNode,
@NotNull MethodNode node,
@NotNull Frame<BasicValue>[] frames
) {
List<BasicValue> 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<out Frame<BasicValue>?>
): List<BasicValue> {
val values = ArrayList<BasicValue>()
val insnList = node.instructions
val localVariableStart = insnList.indexOf(localVariableNode.start)
val localVariableEnd = insnList.indexOf(localVariableNode.end)
Frame<BasicValue> 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<Integer> doubleSizedVars = new HashSet<>();
for (BoxedValueDescriptor valueDescriptor : values) {
private fun buildVariablesRemapping(values: RedundantBoxedValuesCollection, node: MethodNode): IntArray {
val doubleSizedVars = HashSet<Int>()
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<AbstractInsnNode, Type> 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/<T>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/<T>iterator.next<T>())
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<AbstractInsnNode, Type> castWithType
private fun adaptCastInstruction(
node: MethodNode,
value: BoxedValueDescriptor,
castWithType: Pair<AbstractInsnNode, Type>
) {
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))
}
}