KT-15862 Inline generic functions can unexpectedly box primitives
Previous version of the boxing/unboxing analysis treated merging boxed and non-boxed values as a hazard.
If such merged values are not used (e.g., early return + local variables reused in inlined calls),
corresponding boxing/unboxing operations still can be optimized out.
All information related to boxed value usage by instructions is moved to 'BoxedValueDescriptor'.
Introduce "tainted" (and "clean") boxed values, with the following rules:
merge(B, B) = B, if unboxed types are compatible,
T, otherwise
merge(B, X) = T
merge(T, X) = T
where
X is a non-boxed value,
B is a "clean" boxed value,
T is a "tainted" boxed value.
Postpone decision about value merge hazards until a "tainted" value is used.
This commit is contained in:
+49
-34
@@ -20,35 +20,53 @@ import com.intellij.openapi.util.Pair
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue
|
||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||
import org.jetbrains.kotlin.utils.toReadOnlyList
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
|
||||
import java.util.*
|
||||
|
||||
class BoxedBasicValue(
|
||||
abstract class BoxedBasicValue(type: Type) : StrictBasicValue(type) {
|
||||
abstract val descriptor: BoxedValueDescriptor
|
||||
abstract fun taint(): BoxedBasicValue
|
||||
|
||||
override fun equals(other: Any?) = this === other
|
||||
override fun hashCode() = System.identityHashCode(this)
|
||||
}
|
||||
|
||||
|
||||
class CleanBoxedValue(
|
||||
boxedType: Type,
|
||||
boxingInsn: AbstractInsnNode,
|
||||
progressionIterator: ProgressionIteratorBasicValue?
|
||||
) : BoxedBasicValue(boxedType) {
|
||||
override val descriptor = BoxedValueDescriptor(boxedType, boxingInsn, progressionIterator)
|
||||
|
||||
private var tainted: TaintedBoxedValue? = null
|
||||
override fun taint(): BoxedBasicValue = tainted ?: TaintedBoxedValue(this).also { tainted = it }
|
||||
}
|
||||
|
||||
|
||||
class TaintedBoxedValue(val boxedBasicValue: CleanBoxedValue) : BoxedBasicValue(boxedBasicValue.type) {
|
||||
override val descriptor get() = boxedBasicValue.descriptor
|
||||
|
||||
override fun taint(): BoxedBasicValue = this
|
||||
}
|
||||
|
||||
|
||||
class BoxedValueDescriptor(
|
||||
val boxedType: Type,
|
||||
val boxingInsn: AbstractInsnNode,
|
||||
val progressionIterator: ProgressionIteratorBasicValue?
|
||||
) : StrictBasicValue(boxedType) {
|
||||
) {
|
||||
private val associatedInsns = HashSet<AbstractInsnNode>()
|
||||
private val unboxingWithCastInsns = HashSet<Pair<AbstractInsnNode, Type>>()
|
||||
private val associatedVariables = HashSet<Int>()
|
||||
private val mergedWith = HashSet<BoxedBasicValue>()
|
||||
private val mergedWith = HashSet<BoxedValueDescriptor>()
|
||||
|
||||
val primitiveType: Type = unboxType(boxedType)
|
||||
var isSafeToRemove = true; private set
|
||||
val unboxedType: Type = getUnboxedType(boxedType)
|
||||
|
||||
override fun equals(other: Any?) =
|
||||
this === other
|
||||
|
||||
fun typeEquals(other: BasicValue) =
|
||||
other is BoxedBasicValue && type == other.type
|
||||
|
||||
override fun hashCode() =
|
||||
System.identityHashCode(this)
|
||||
|
||||
fun getAssociatedInsns(): List<AbstractInsnNode> =
|
||||
ArrayList(associatedInsns)
|
||||
fun getAssociatedInsns() = associatedInsns.toReadOnlyList()
|
||||
|
||||
fun addInsn(insnNode: AbstractInsnNode) {
|
||||
associatedInsns.add(insnNode)
|
||||
@@ -61,22 +79,20 @@ class BoxedBasicValue(
|
||||
fun getVariablesIndexes(): List<Int> =
|
||||
ArrayList(associatedVariables)
|
||||
|
||||
fun addMergedWith(value: BoxedBasicValue) {
|
||||
mergedWith.add(value)
|
||||
fun addMergedWith(descriptor: BoxedValueDescriptor) {
|
||||
mergedWith.add(descriptor)
|
||||
}
|
||||
|
||||
fun getMergedWith(): Iterable<BoxedBasicValue> =
|
||||
fun getMergedWith(): Iterable<BoxedValueDescriptor> =
|
||||
mergedWith
|
||||
|
||||
fun markAsUnsafeToRemove() {
|
||||
isSafeToRemove = false
|
||||
}
|
||||
|
||||
fun isDoubleSize() =
|
||||
primitiveType.size == 2
|
||||
fun isDoubleSize() = unboxedType.size == 2
|
||||
|
||||
fun isFromProgressionIterator() =
|
||||
progressionIterator != null
|
||||
fun isFromProgressionIterator() = progressionIterator != null
|
||||
|
||||
fun addUnboxingWithCastTo(insn: AbstractInsnNode, type: Type) {
|
||||
unboxingWithCastInsns.add(Pair.create(insn, type))
|
||||
@@ -84,15 +100,14 @@ class BoxedBasicValue(
|
||||
|
||||
fun getUnboxingWithCastInsns(): Set<Pair<AbstractInsnNode, Type>> =
|
||||
unboxingWithCastInsns
|
||||
|
||||
companion object {
|
||||
private fun unboxType(boxedType: Type): Type {
|
||||
val primitiveType = AsmUtil.unboxPrimitiveTypeOrNull(boxedType)
|
||||
if (primitiveType != null) return primitiveType
|
||||
|
||||
if (boxedType == AsmTypes.K_CLASS_TYPE) return AsmTypes.JAVA_CLASS_TYPE
|
||||
|
||||
throw IllegalArgumentException("Expected primitive type wrapper or KClass, got: $boxedType")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun getUnboxedType(boxedType: Type): Type {
|
||||
val primitiveType = AsmUtil.unboxPrimitiveTypeOrNull(boxedType)
|
||||
if (primitiveType != null) return primitiveType
|
||||
|
||||
if (boxedType == AsmTypes.K_CLASS_TYPE) return AsmTypes.JAVA_CLASS_TYPE
|
||||
|
||||
throw IllegalArgumentException("Expected primitive type wrapper or KClass, got: $boxedType")
|
||||
}
|
||||
|
||||
+36
-27
@@ -29,7 +29,6 @@ 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.MethodInsnNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
|
||||
import java.util.*
|
||||
|
||||
@@ -39,14 +38,23 @@ open class BoxingInterpreter(private val insnList: InsnList) : OptimizationBasic
|
||||
protected open fun createNewBoxing(insn: AbstractInsnNode, type: Type, progressionIterator: ProgressionIteratorBasicValue?): BasicValue {
|
||||
val index = insnList.indexOf(insn)
|
||||
return boxingPlaces.getOrPut(index) {
|
||||
val boxedBasicValue = BoxedBasicValue(type, insn, progressionIterator)
|
||||
val boxedBasicValue = CleanBoxedValue(type, insn, progressionIterator)
|
||||
onNewBoxedValue(boxedBasicValue)
|
||||
boxedBasicValue
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(AnalyzerException::class)
|
||||
protected fun checkUsedValue(value: BasicValue) {
|
||||
if (value is TaintedBoxedValue) {
|
||||
onMergeFail(value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun naryOperation(insn: AbstractInsnNode, values: List<BasicValue>): BasicValue? {
|
||||
values.forEach {
|
||||
checkUsedValue(it)
|
||||
}
|
||||
|
||||
val value = super.naryOperation(insn, values)
|
||||
val firstArg = values.firstOrNull() ?: return value
|
||||
|
||||
@@ -80,36 +88,39 @@ open class BoxingInterpreter(private val insnList: InsnList) : OptimizationBasic
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(AnalyzerException::class)
|
||||
override fun unaryOperation(insn: AbstractInsnNode, value: BasicValue): BasicValue? =
|
||||
if (insn.opcode == Opcodes.CHECKCAST && isExactValue(value))
|
||||
value
|
||||
else
|
||||
super.unaryOperation(insn, value)
|
||||
override fun unaryOperation(insn: AbstractInsnNode, value: BasicValue): BasicValue? {
|
||||
checkUsedValue(value)
|
||||
|
||||
return if (insn.opcode == Opcodes.CHECKCAST && isExactValue(value))
|
||||
value
|
||||
else
|
||||
super.unaryOperation(insn, value)
|
||||
}
|
||||
|
||||
protected open fun isExactValue(value: BasicValue) =
|
||||
value is ProgressionIteratorBasicValue ||
|
||||
value is BoxedBasicValue ||
|
||||
value is CleanBoxedValue ||
|
||||
value.type != null && isProgressionClass(value.type.internalName)
|
||||
|
||||
override fun merge(v: BasicValue, w: BasicValue) =
|
||||
when {
|
||||
v == StrictBasicValue.UNINITIALIZED_VALUE || w == StrictBasicValue.UNINITIALIZED_VALUE -> {
|
||||
v == StrictBasicValue.UNINITIALIZED_VALUE || w == StrictBasicValue.UNINITIALIZED_VALUE ->
|
||||
StrictBasicValue.UNINITIALIZED_VALUE
|
||||
}
|
||||
v is BoxedBasicValue && v.typeEquals(w) -> {
|
||||
onMergeSuccess(v, w as BoxedBasicValue)
|
||||
v
|
||||
}
|
||||
else -> {
|
||||
if (v is BoxedBasicValue) {
|
||||
onMergeFail(v)
|
||||
}
|
||||
if (w is BoxedBasicValue) {
|
||||
onMergeFail(w)
|
||||
v is BoxedBasicValue && w is BoxedBasicValue -> {
|
||||
onMergeSuccess(v, w)
|
||||
when {
|
||||
v is TaintedBoxedValue -> v
|
||||
w is TaintedBoxedValue -> w
|
||||
v.type != w.type -> v.taint()
|
||||
else -> v
|
||||
}
|
||||
}
|
||||
v is BoxedBasicValue ->
|
||||
v.taint()
|
||||
w is BoxedBasicValue ->
|
||||
w.taint()
|
||||
else ->
|
||||
super.merge(v, w)
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun onNewBoxedValue(value: BoxedBasicValue) {}
|
||||
@@ -197,7 +208,5 @@ 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")
|
||||
RangeCodegenUtil.getPrimitiveRangeOrProgressionElementType(buildFqNameByInternal(progressionClassInternalName))
|
||||
?.typeName?.asString() ?: error("type should be not null")
|
||||
|
||||
+12
-12
@@ -22,25 +22,25 @@ import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
public class RedundantBoxedValuesCollection implements Iterable<BoxedBasicValue> {
|
||||
private final Set<BoxedBasicValue> safeToDeleteValues = new HashSet<BoxedBasicValue>();
|
||||
public class RedundantBoxedValuesCollection implements Iterable<BoxedValueDescriptor> {
|
||||
private final Set<BoxedValueDescriptor> safeToDeleteValues = new HashSet<BoxedValueDescriptor>();
|
||||
|
||||
public void add(@NotNull BoxedBasicValue value) {
|
||||
safeToDeleteValues.add(value);
|
||||
public void add(@NotNull BoxedValueDescriptor descriptor) {
|
||||
safeToDeleteValues.add(descriptor);
|
||||
}
|
||||
|
||||
public void remove(@NotNull BoxedBasicValue value) {
|
||||
if (safeToDeleteValues.contains(value)) {
|
||||
safeToDeleteValues.remove(value);
|
||||
value.markAsUnsafeToRemove();
|
||||
public void remove(@NotNull BoxedValueDescriptor descriptor) {
|
||||
if (safeToDeleteValues.contains(descriptor)) {
|
||||
safeToDeleteValues.remove(descriptor);
|
||||
descriptor.markAsUnsafeToRemove();
|
||||
|
||||
for (BoxedBasicValue mergedValue : value.getMergedWith()) {
|
||||
remove(mergedValue);
|
||||
for (BoxedValueDescriptor mergedValueDescriptor : descriptor.getMergedWith()) {
|
||||
remove(mergedValueDescriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void merge(@NotNull BoxedBasicValue v, @NotNull BoxedBasicValue w) {
|
||||
public void merge(@NotNull BoxedValueDescriptor v, @NotNull BoxedValueDescriptor w) {
|
||||
v.addMergedWith(w);
|
||||
w.addMergedWith(v);
|
||||
|
||||
@@ -59,7 +59,7 @@ public class RedundantBoxedValuesCollection implements Iterable<BoxedBasicValue>
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Iterator<BoxedBasicValue> iterator() {
|
||||
public Iterator<BoxedValueDescriptor> iterator() {
|
||||
return safeToDeleteValues.iterator();
|
||||
}
|
||||
}
|
||||
|
||||
+31
-36
@@ -23,30 +23,12 @@ import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.InsnList
|
||||
import org.jetbrains.org.objectweb.asm.tree.TypeInsnNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.VarInsnNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
|
||||
|
||||
internal class RedundantBoxingInterpreter(insnList: InsnList) : BoxingInterpreter(insnList) {
|
||||
|
||||
val candidatesBoxedValues = RedundantBoxedValuesCollection()
|
||||
|
||||
@Throws(AnalyzerException::class)
|
||||
override fun binaryOperation(insn: AbstractInsnNode, value1: BasicValue, value2: BasicValue): BasicValue? {
|
||||
processOperationWithBoxedValue(value1, insn)
|
||||
processOperationWithBoxedValue(value2, insn)
|
||||
|
||||
return super.binaryOperation(insn, value1, value2)
|
||||
}
|
||||
|
||||
@Throws(AnalyzerException::class)
|
||||
override fun ternaryOperation(insn: AbstractInsnNode, value1: BasicValue, value2: BasicValue, value3: BasicValue): BasicValue? {
|
||||
// in a valid code only aastore could happen with boxed value
|
||||
processOperationWithBoxedValue(value3, insn)
|
||||
|
||||
return super.ternaryOperation(insn, value1, value2, value3)
|
||||
}
|
||||
|
||||
@Throws(AnalyzerException::class)
|
||||
override fun unaryOperation(insn: AbstractInsnNode, value: BasicValue): BasicValue? {
|
||||
if ((insn.opcode == Opcodes.CHECKCAST || insn.opcode == Opcodes.INSTANCEOF) && value is BoxedBasicValue) {
|
||||
val typeInsn = insn as TypeInsnNode
|
||||
@@ -61,10 +43,23 @@ internal class RedundantBoxingInterpreter(insnList: InsnList) : BoxingInterprete
|
||||
return super.unaryOperation(insn, value)
|
||||
}
|
||||
|
||||
@Throws(AnalyzerException::class)
|
||||
override fun binaryOperation(insn: AbstractInsnNode, value1: BasicValue, value2: BasicValue): BasicValue? {
|
||||
processOperationWithBoxedValue(value1, insn)
|
||||
processOperationWithBoxedValue(value2, insn)
|
||||
|
||||
return super.binaryOperation(insn, value1, value2)
|
||||
}
|
||||
|
||||
override fun ternaryOperation(insn: AbstractInsnNode, value1: BasicValue, value2: BasicValue, value3: BasicValue): BasicValue? {
|
||||
// in a valid code only aastore could happen with boxed value
|
||||
processOperationWithBoxedValue(value3, insn)
|
||||
|
||||
return super.ternaryOperation(insn, value1, value2, value3)
|
||||
}
|
||||
|
||||
override fun copyOperation(insn: AbstractInsnNode, value: BasicValue): BasicValue {
|
||||
if (value is BoxedBasicValue && insn.opcode === Opcodes.ASTORE) {
|
||||
value.addVariableIndex((insn as VarInsnNode).`var`)
|
||||
if (value is BoxedBasicValue && insn.opcode == Opcodes.ASTORE) {
|
||||
value.descriptor.addVariableIndex((insn as VarInsnNode).`var`)
|
||||
}
|
||||
|
||||
processOperationWithBoxedValue(value, insn)
|
||||
@@ -77,15 +72,15 @@ internal class RedundantBoxingInterpreter(insnList: InsnList) : BoxingInterprete
|
||||
}
|
||||
|
||||
override fun onNewBoxedValue(value: BoxedBasicValue) {
|
||||
candidatesBoxedValues.add(value)
|
||||
candidatesBoxedValues.add(value.descriptor)
|
||||
}
|
||||
|
||||
override fun onUnboxing(insn: AbstractInsnNode, value: BoxedBasicValue, resultType: Type) {
|
||||
if (value.primitiveType == resultType) {
|
||||
addAssociatedInsn(value, insn)
|
||||
}
|
||||
else {
|
||||
value.addUnboxingWithCastTo(insn, resultType)
|
||||
value.descriptor.run {
|
||||
if (unboxedType == resultType)
|
||||
addAssociatedInsn(value, insn)
|
||||
else
|
||||
addUnboxingWithCastTo(insn, resultType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,11 +93,13 @@ internal class RedundantBoxingInterpreter(insnList: InsnList) : BoxingInterprete
|
||||
}
|
||||
|
||||
override fun onMergeSuccess(v: BoxedBasicValue, w: BoxedBasicValue) {
|
||||
candidatesBoxedValues.merge(v, w)
|
||||
candidatesBoxedValues.merge(v.descriptor, w.descriptor)
|
||||
}
|
||||
|
||||
private fun processOperationWithBoxedValue(value: BasicValue?, insnNode: AbstractInsnNode) {
|
||||
if (value is BoxedBasicValue) {
|
||||
checkUsedValue(value)
|
||||
|
||||
if (!PERMITTED_OPERATIONS_OPCODES.contains(insnNode.opcode)) {
|
||||
markValueAsDirty(value)
|
||||
}
|
||||
@@ -113,7 +110,7 @@ internal class RedundantBoxingInterpreter(insnList: InsnList) : BoxingInterprete
|
||||
}
|
||||
|
||||
private fun markValueAsDirty(value: BoxedBasicValue) {
|
||||
candidatesBoxedValues.remove(value)
|
||||
candidatesBoxedValues.remove(value.descriptor)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -127,17 +124,15 @@ internal class RedundantBoxingInterpreter(insnList: InsnList) : BoxingInterprete
|
||||
when (targetInternalName) {
|
||||
Type.getInternalName(Any::class.java) ->
|
||||
true
|
||||
Type.getInternalName(Number::class.java) -> {
|
||||
PRIMITIVE_TYPES_SORTS_WITH_WRAPPER_EXTENDS_NUMBER.contains(
|
||||
value.primitiveType.sort)
|
||||
}
|
||||
Type.getInternalName(Number::class.java) ->
|
||||
PRIMITIVE_TYPES_SORTS_WITH_WRAPPER_EXTENDS_NUMBER.contains(value.descriptor.unboxedType.sort)
|
||||
else ->
|
||||
value.type.internalName.equals(targetInternalName)
|
||||
value.type.internalName == targetInternalName
|
||||
}
|
||||
|
||||
private fun addAssociatedInsn(value: BoxedBasicValue, insn: AbstractInsnNode) {
|
||||
if (value.isSafeToRemove) {
|
||||
value.addInsn(insn)
|
||||
value.descriptor.run {
|
||||
if (isSafeToRemove) addInsn(insn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+42
-35
@@ -16,8 +16,6 @@
|
||||
|
||||
package org.jetbrains.kotlin.codegen.optimization.boxing;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import kotlin.collections.CollectionsKt;
|
||||
import kotlin.jvm.functions.Function1;
|
||||
@@ -38,9 +36,8 @@ public class RedundantBoxingMethodTransformer extends MethodTransformer {
|
||||
@Override
|
||||
public void transform(@NotNull String internalClassName, @NotNull MethodNode node) {
|
||||
RedundantBoxingInterpreter interpreter = new RedundantBoxingInterpreter(node.instructions);
|
||||
Frame<BasicValue>[] frames = analyze(
|
||||
internalClassName, node, interpreter
|
||||
);
|
||||
Frame<BasicValue>[] frames = analyze(internalClassName, node, interpreter);
|
||||
|
||||
interpretPopInstructionsForBoxedValues(interpreter, node, frames);
|
||||
|
||||
RedundantBoxedValuesCollection valuesToOptimize = interpreter.getCandidatesBoxedValues();
|
||||
@@ -99,32 +96,25 @@ public class RedundantBoxingMethodTransformer extends MethodTransformer {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<BasicValue> usedValues = getValuesStoredOrLoadedToVariable(localVariableNode, node, frames);
|
||||
List<BasicValue> variableValues = getValuesStoredOrLoadedToVariable(localVariableNode, node, frames);
|
||||
|
||||
Collection<BasicValue> boxed = Collections2.filter(usedValues, new Predicate<BasicValue>() {
|
||||
Collection<BasicValue> boxed = CollectionsKt.filter(variableValues, new Function1<BasicValue, Boolean>() {
|
||||
@Override
|
||||
public boolean apply(BasicValue input) {
|
||||
return input instanceof BoxedBasicValue;
|
||||
public Boolean invoke(BasicValue value) {
|
||||
return value instanceof BoxedBasicValue;
|
||||
}
|
||||
});
|
||||
|
||||
if (boxed.isEmpty()) continue;
|
||||
|
||||
final BoxedBasicValue firstBoxed = (BoxedBasicValue) boxed.iterator().next();
|
||||
BoxedValueDescriptor firstBoxed = ((BoxedBasicValue) boxed.iterator().next()).getDescriptor();
|
||||
if (isUnsafeToRemoveBoxingForConnectedValues(variableValues, firstBoxed.getUnboxedType())) {
|
||||
for (BasicValue value : variableValues) {
|
||||
if (!(value instanceof BoxedBasicValue)) continue;
|
||||
|
||||
if (CollectionsKt.any(usedValues, new Function1<BasicValue, Boolean>() {
|
||||
@Override
|
||||
public Boolean invoke(BasicValue input) {
|
||||
if (input == StrictBasicValue.UNINITIALIZED_VALUE) return false;
|
||||
return input == null ||
|
||||
!(input instanceof BoxedBasicValue) ||
|
||||
!((BoxedBasicValue) input).isSafeToRemove() ||
|
||||
!((BoxedBasicValue) input).getPrimitiveType().equals(firstBoxed.getPrimitiveType());
|
||||
}
|
||||
})) {
|
||||
for (BasicValue value : usedValues) {
|
||||
if (value instanceof BoxedBasicValue && ((BoxedBasicValue) value).isSafeToRemove()) {
|
||||
values.remove((BoxedBasicValue) value);
|
||||
BoxedValueDescriptor descriptor = ((BoxedBasicValue) value).getDescriptor();
|
||||
if (descriptor.isSafeToRemove()) {
|
||||
values.remove(descriptor);
|
||||
needToRepeat = true;
|
||||
}
|
||||
}
|
||||
@@ -134,6 +124,20 @@ public class RedundantBoxingMethodTransformer extends MethodTransformer {
|
||||
return needToRepeat;
|
||||
}
|
||||
|
||||
private static boolean isUnsafeToRemoveBoxingForConnectedValues(List<BasicValue> usedValues, final Type unboxedType) {
|
||||
return CollectionsKt.any(usedValues, new Function1<BasicValue, Boolean>() {
|
||||
@Override
|
||||
public Boolean invoke(BasicValue input) {
|
||||
if (input == StrictBasicValue.UNINITIALIZED_VALUE) return false;
|
||||
if (!(input instanceof BoxedBasicValue)) return 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) {
|
||||
@@ -141,8 +145,11 @@ public class RedundantBoxingMethodTransformer extends MethodTransformer {
|
||||
}
|
||||
|
||||
for (BasicValue value : getValuesStoredOrLoadedToVariable(localVariableNode, node, frames)) {
|
||||
if (value == null || !(value instanceof BoxedBasicValue) || !((BoxedBasicValue) value).isSafeToRemove()) continue;
|
||||
localVariableNode.desc = ((BoxedBasicValue) value).getPrimitiveType().getDescriptor();
|
||||
if (!(value instanceof BoxedBasicValue)) continue;
|
||||
|
||||
BoxedValueDescriptor descriptor = ((BoxedBasicValue) value).getDescriptor();
|
||||
if (!descriptor.isSafeToRemove()) continue;
|
||||
localVariableNode.desc = descriptor.getUnboxedType().getDescriptor();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,9 +200,9 @@ public class RedundantBoxingMethodTransformer extends MethodTransformer {
|
||||
@NotNull
|
||||
private static int[] buildVariablesRemapping(@NotNull RedundantBoxedValuesCollection values, @NotNull MethodNode node) {
|
||||
Set<Integer> doubleSizedVars = new HashSet<Integer>();
|
||||
for (BoxedBasicValue value : values) {
|
||||
if (value.getPrimitiveType().getSize() == 2) {
|
||||
doubleSizedVars.addAll(value.getVariablesIndexes());
|
||||
for (BoxedValueDescriptor valueDescriptor : values) {
|
||||
if (valueDescriptor.isDoubleSize()) {
|
||||
doubleSizedVars.addAll(valueDescriptor.getVariablesIndexes());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,12 +240,12 @@ public class RedundantBoxingMethodTransformer extends MethodTransformer {
|
||||
@NotNull MethodNode node,
|
||||
@NotNull RedundantBoxedValuesCollection values
|
||||
) {
|
||||
for (BoxedBasicValue value : values) {
|
||||
for (BoxedValueDescriptor value : values) {
|
||||
adaptInstructionsForBoxedValue(node, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static void adaptInstructionsForBoxedValue(@NotNull MethodNode node, @NotNull BoxedBasicValue value) {
|
||||
private static void adaptInstructionsForBoxedValue(@NotNull MethodNode node, @NotNull BoxedValueDescriptor value) {
|
||||
adaptBoxingInstruction(node, value);
|
||||
|
||||
for (Pair<AbstractInsnNode, Type> cast : value.getUnboxingWithCastInsns()) {
|
||||
@@ -250,7 +257,7 @@ public class RedundantBoxingMethodTransformer extends MethodTransformer {
|
||||
}
|
||||
}
|
||||
|
||||
private static void adaptBoxingInstruction(@NotNull MethodNode node, @NotNull BoxedBasicValue value) {
|
||||
private static void adaptBoxingInstruction(@NotNull MethodNode node, @NotNull BoxedValueDescriptor value) {
|
||||
if (!value.isFromProgressionIterator()) {
|
||||
node.instructions.remove(value.getBoxingInsn());
|
||||
}
|
||||
@@ -280,12 +287,12 @@ public class RedundantBoxingMethodTransformer extends MethodTransformer {
|
||||
|
||||
private static void adaptCastInstruction(
|
||||
@NotNull MethodNode node,
|
||||
@NotNull BoxedBasicValue value,
|
||||
@NotNull BoxedValueDescriptor value,
|
||||
@NotNull Pair<AbstractInsnNode, Type> castWithType
|
||||
) {
|
||||
AbstractInsnNode castInsn = castWithType.getFirst();
|
||||
MethodNode castInsnsListener = new MethodNode(Opcodes.ASM5);
|
||||
new InstructionAdapter(castInsnsListener).cast(value.getPrimitiveType(), castWithType.getSecond());
|
||||
new InstructionAdapter(castInsnsListener).cast(value.getUnboxedType(), castWithType.getSecond());
|
||||
|
||||
for (AbstractInsnNode insn : castInsnsListener.instructions.toArray()) {
|
||||
node.instructions.insertBefore(castInsn, insn);
|
||||
@@ -295,7 +302,7 @@ public class RedundantBoxingMethodTransformer extends MethodTransformer {
|
||||
}
|
||||
|
||||
private static void adaptInstruction(
|
||||
@NotNull MethodNode node, @NotNull AbstractInsnNode insn, @NotNull BoxedBasicValue value
|
||||
@NotNull MethodNode node, @NotNull AbstractInsnNode insn, @NotNull BoxedValueDescriptor value
|
||||
) {
|
||||
boolean isDoubleSize = value.isDoubleSize();
|
||||
|
||||
@@ -322,7 +329,7 @@ public class RedundantBoxingMethodTransformer extends MethodTransformer {
|
||||
node.instructions.set(
|
||||
insn,
|
||||
new VarInsnNode(
|
||||
value.getPrimitiveType().getOpcode(intVarOpcode),
|
||||
value.getUnboxedType().getOpcode(intVarOpcode),
|
||||
((VarInsnNode) insn).var
|
||||
)
|
||||
);
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
// Just make sure there's no VerifyError
|
||||
|
||||
fun getOrElse() =
|
||||
mapOf<String, Int>().getOrElse("foo") { 3 }
|
||||
|
||||
fun isNotEmpty(l: ArrayList<Int>) =
|
||||
l.iterator()?.hasNext() ?: false
|
||||
|
||||
fun box() = "OK"
|
||||
@@ -0,0 +1,49 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
inline fun <T> put(
|
||||
x: T,
|
||||
maxExclusive: Int,
|
||||
isEmpty: (Int) -> Boolean,
|
||||
equals: (T, T) -> Boolean,
|
||||
fetch: (Int) -> T,
|
||||
store: (Int, T) -> Unit
|
||||
): Boolean {
|
||||
var i = 0
|
||||
do {
|
||||
if (isEmpty(i)) {
|
||||
store(i, x)
|
||||
return true
|
||||
}
|
||||
|
||||
val y = fetch(i)
|
||||
if (equals(x, y)) {
|
||||
return false
|
||||
}
|
||||
|
||||
i++
|
||||
if (i >= maxExclusive) return false
|
||||
} while (true)
|
||||
}
|
||||
|
||||
const val SIZE = 16
|
||||
val arr = IntArray(SIZE) { -1 }
|
||||
|
||||
fun putNonNegInt(x: Int) =
|
||||
put(x, SIZE,
|
||||
isEmpty = { arr[it] < 0 },
|
||||
equals = { x, y -> x == y },
|
||||
fetch = { arr[it] },
|
||||
store = { i, x -> arr[i] = x }
|
||||
)
|
||||
|
||||
fun box(): String {
|
||||
putNonNegInt(1)
|
||||
putNonNegInt(2)
|
||||
putNonNegInt(3)
|
||||
|
||||
if (arr[0] != 1) return "Fail, ${arr.toList().toString()}"
|
||||
if (arr[1] != 2) return "Fail, ${arr.toList().toString()}"
|
||||
if (arr[2] != 3) return "Fail, ${arr.toList().toString()}"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// FILE: test.kt
|
||||
|
||||
// @TestKt.class:
|
||||
// 0 valueOf
|
||||
// 0 Value\s\(\)
|
||||
|
||||
val mask = 127
|
||||
val entries = IntArray(128)
|
||||
val flags = BooleanArray(128)
|
||||
|
||||
fun distance(index: Int, hash: Int): Int = (128 + index - (hash and mask)) and mask
|
||||
|
||||
fun insertSad(x: Int): Boolean {
|
||||
return insertWithBoxing(
|
||||
x,
|
||||
hash = { it },
|
||||
equals = { a, b -> a == b },
|
||||
isEmpty = { !flags[it] },
|
||||
fetch = { entries[it] },
|
||||
store = { i, x -> entries[i] = x; flags[i] = true; }
|
||||
)
|
||||
}
|
||||
|
||||
// FILE: inline.kt
|
||||
inline fun <T> insertWithBoxing(entry: T,
|
||||
hash: (T) -> Int,
|
||||
equals: (T, T) -> Boolean,
|
||||
isEmpty: (Int) -> Boolean,
|
||||
fetch: (Int) -> T,
|
||||
store: (Int, T) -> Unit): Boolean {
|
||||
var currentEntry = entry
|
||||
var index = hash(entry) and mask
|
||||
var dist = 0
|
||||
do {
|
||||
if (isEmpty(index)) {
|
||||
store(index, currentEntry)
|
||||
return true
|
||||
}
|
||||
|
||||
val existingEntry = fetch(index)
|
||||
if (equals(existingEntry, currentEntry)) {
|
||||
return false
|
||||
}
|
||||
|
||||
val existingHash = hash(existingEntry)
|
||||
val existingDistance = distance(index, existingHash)
|
||||
if (existingDistance < dist) {
|
||||
store(index, currentEntry)
|
||||
currentEntry = existingEntry
|
||||
dist = existingDistance
|
||||
}
|
||||
|
||||
dist += 1
|
||||
index = (index + 1) and mask
|
||||
}
|
||||
while (true)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// FILE: test.kt
|
||||
|
||||
// @TestKt.class:
|
||||
// 0 valueOf
|
||||
// 0 Value\s\(\)
|
||||
|
||||
const val SIZE = 16
|
||||
val arr = IntArray(SIZE) { -1 }
|
||||
|
||||
fun putNonNegInt(x: Int) =
|
||||
put(x, SIZE,
|
||||
isEmpty = { arr[it] < 0 },
|
||||
equals = { x, y -> x == y },
|
||||
fetch = { arr[it] },
|
||||
store = { i, x -> arr[i] = x }
|
||||
)
|
||||
|
||||
// FILE: inline.kt
|
||||
inline fun <T> put(
|
||||
x: T,
|
||||
maxExclusive: Int,
|
||||
isEmpty: (Int) -> Boolean,
|
||||
equals: (T, T) -> Boolean,
|
||||
fetch: (Int) -> T,
|
||||
store: (Int, T) -> Unit
|
||||
): Boolean {
|
||||
var i = 0
|
||||
do {
|
||||
if (isEmpty(i)) {
|
||||
store(i, x)
|
||||
return true
|
||||
}
|
||||
|
||||
val y = fetch(i)
|
||||
if (equals(x, y)) {
|
||||
return false
|
||||
}
|
||||
|
||||
i++
|
||||
if (i >= maxExclusive) return false
|
||||
} while (true)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
@kotlin.Metadata
|
||||
public final class TaintedValuesKt {
|
||||
public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String
|
||||
public final static method getOrElse(): int
|
||||
public final static method isNotEmpty(@org.jetbrains.annotations.NotNull p0: java.util.ArrayList): boolean
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
@kotlin.Metadata
|
||||
public final class TaintedValuesBoxKt {
|
||||
public final static field SIZE: int
|
||||
private final static @org.jetbrains.annotations.NotNull field arr: int[]
|
||||
public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String
|
||||
public final static @org.jetbrains.annotations.NotNull method getArr(): int[]
|
||||
public final static method put(p0: java.lang.Object, p1: int, @org.jetbrains.annotations.NotNull p2: kotlin.jvm.functions.Function1, @org.jetbrains.annotations.NotNull p3: kotlin.jvm.functions.Function2, @org.jetbrains.annotations.NotNull p4: kotlin.jvm.functions.Function1, @org.jetbrains.annotations.NotNull p5: kotlin.jvm.functions.Function2): boolean
|
||||
public final static method putNonNegInt(p0: int): boolean
|
||||
}
|
||||
+12
@@ -938,6 +938,18 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("taintedValues.kt")
|
||||
public void testTaintedValues() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/taintedValues.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("taintedValuesBox.kt")
|
||||
public void testTaintedValuesBox() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/taintedValuesBox.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("unsafeRemoving.kt")
|
||||
public void testUnsafeRemoving() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/unsafeRemoving.kt");
|
||||
|
||||
@@ -938,6 +938,18 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("taintedValues.kt")
|
||||
public void testTaintedValues() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/taintedValues.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("taintedValuesBox.kt")
|
||||
public void testTaintedValuesBox() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/taintedValuesBox.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("unsafeRemoving.kt")
|
||||
public void testUnsafeRemoving() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/unsafeRemoving.kt");
|
||||
|
||||
@@ -473,6 +473,18 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("kt15862.kt")
|
||||
public void testKt15862() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/boxingOptimization/kt15862.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("kt15862_2.kt")
|
||||
public void testKt15862_2() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/boxingOptimization/kt15862_2.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("kt6842.kt")
|
||||
public void testKt6842() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/boxingOptimization/kt6842.kt");
|
||||
|
||||
+12
@@ -938,6 +938,18 @@ public class LightAnalysisModeCodegenTestGenerated extends AbstractLightAnalysis
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("taintedValues.kt")
|
||||
public void testTaintedValues() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/taintedValues.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("taintedValuesBox.kt")
|
||||
public void testTaintedValuesBox() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/taintedValuesBox.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("unsafeRemoving.kt")
|
||||
public void testUnsafeRemoving() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/unsafeRemoving.kt");
|
||||
|
||||
+12
@@ -1160,6 +1160,18 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("taintedValues.kt")
|
||||
public void testTaintedValues() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/taintedValues.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("taintedValuesBox.kt")
|
||||
public void testTaintedValuesBox() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/taintedValuesBox.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("unsafeRemoving.kt")
|
||||
public void testUnsafeRemoving() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/boxingOptimization/unsafeRemoving.kt");
|
||||
|
||||
Reference in New Issue
Block a user