Dealing with case when boxing value is being stored in a variable

This commit is contained in:
Denis Zharkov
2014-07-03 18:01:40 +04:00
committed by Alexander Udalov
parent b2eaac4468
commit ee87b11723
5 changed files with 408 additions and 73 deletions
@@ -28,27 +28,33 @@ import java.util.Set;
public class BoxedBasicValue extends BasicValue {
private final Set<AbstractInsnNode> associatedInsns = new HashSet<AbstractInsnNode>();
private final AbstractInsnNode boxingInsn;
private final Set<Integer> associatedVariables = new HashSet<Integer>();
private final Set<BoxedBasicValue> mergedWith = new HashSet<BoxedBasicValue>();
private final Type primitiveType;
private boolean wasUnboxed = false;
private boolean isSafeToRemove = true;
public BoxedBasicValue(Type primitiveType, AbstractInsnNode insnNode) {
super(AsmUtil.boxType(primitiveType));
this.primitiveType = primitiveType;
public BoxedBasicValue(Type boxedType, AbstractInsnNode insnNode) {
super(boxedType);
this.primitiveType = AsmUtil.unboxType(boxedType);
associatedInsns.add(insnNode);
boxingInsn = insnNode;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
return this == o;
}
public boolean typeEquals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
BoxedBasicValue that = (BoxedBasicValue) o;
if (!getType().equals(that.getType())) return false;
return getType().equals(that.getType());
}
return (boxingInsn == ((BoxedBasicValue) o).boxingInsn);
@Override
public int hashCode() {
return System.identityHashCode(this);
}
public List<AbstractInsnNode> getAssociatedInsns() {
@@ -59,15 +65,50 @@ public class BoxedBasicValue extends BasicValue {
associatedInsns.add(insnNode);
}
public void addVariableIndex(int index) {
associatedVariables.add(index);
}
public List<Integer> getVariablesIndexes() {
return new ArrayList<Integer>(associatedVariables);
}
public Type getPrimitiveType() {
return primitiveType;
}
public boolean wasUnboxed() {
return wasUnboxed;
public void mergeWith(BoxedBasicValue value) {
if (this == value) {
return;
}
if (!value.isSafeToRemove) {
propagateRemovingAsUnsafe();
}
if (!isSafeToRemove) {
value.propagateRemovingAsUnsafe();
}
mergedWith.add(value);
value.mergedWith.add(this);
}
public void setWasUnboxed(boolean wasUnboxed) {
this.wasUnboxed = wasUnboxed;
public void propagateRemovingAsUnsafe() {
isSafeToRemove = false;
for (BoxedBasicValue value : mergedWith) {
if (value.isSafeToRemove()) {
value.propagateRemovingAsUnsafe();
}
}
}
public boolean isSafeToRemove() {
return isSafeToRemove;
}
public boolean isDoubleSize() {
return getPrimitiveType().getSize() == 2;
}
}
@@ -94,7 +94,7 @@ public class BoxingInterpreter extends OptimizationBasicInterpreter {
if (isBoxing(insn)) {
int index = insnList.indexOf(insn);
if (!boxingPlaces.containsKey(index)) {
BoxedBasicValue boxedBasicValue = new BoxedBasicValue(values.get(0).getType(), insn);
BoxedBasicValue boxedBasicValue = new BoxedBasicValue(value.getType(), insn);
onNewBoxedValue(boxedBasicValue);
boxingPlaces.put(index, boxedBasicValue);
}
@@ -105,9 +105,7 @@ public class BoxingInterpreter extends OptimizationBasicInterpreter {
values.get(0) instanceof BoxedBasicValue &&
value.getType().equals(((BoxedBasicValue) values.get(0)).getPrimitiveType())) {
BoxedBasicValue boxedBasicValue = (BoxedBasicValue) values.get(0);
boxedBasicValue.addInsn(insn);
boxedBasicValue.setWasUnboxed(true);
onUnboxing((BoxedBasicValue) values.get(0), insn);
}
else {
for (BasicValue arg : values) {
@@ -120,12 +118,10 @@ public class BoxingInterpreter extends OptimizationBasicInterpreter {
return value;
}
@Override
@Nullable
public BasicValue unaryOperation(@NotNull AbstractInsnNode insn, @NotNull BasicValue value) throws AnalyzerException {
if (isAllowedUnaryOperationWithBoxed(insn.getOpcode()) && value instanceof BoxedBasicValue) {
((BoxedBasicValue) value).addInsn(insn);
if (insn.getOpcode() == Opcodes.CHECKCAST && value instanceof BoxedBasicValue) {
return value;
}
@@ -135,7 +131,8 @@ public class BoxingInterpreter extends OptimizationBasicInterpreter {
@Override
@NotNull
public BasicValue merge(@NotNull BasicValue v, @NotNull BasicValue w) {
if (v instanceof BoxedBasicValue && v.equals(w)) {
if (v instanceof BoxedBasicValue && ((BoxedBasicValue) v).typeEquals(w)) {
((BoxedBasicValue) v).mergeWith((BoxedBasicValue) w);
return v;
}
@@ -153,19 +150,19 @@ public class BoxingInterpreter extends OptimizationBasicInterpreter {
}
protected void onNewBoxedValue(BoxedBasicValue value) {
protected void onNewBoxedValue(@NotNull BoxedBasicValue value) {
}
protected void onMethodCallWithBoxedValue(BoxedBasicValue value) {
protected void onUnboxing(@NotNull BoxedBasicValue value, @NotNull AbstractInsnNode insn) {
}
protected void onMergeFail(BoxedBasicValue value) {
protected void onMethodCallWithBoxedValue(@NotNull BoxedBasicValue value) {
}
protected boolean isAllowedUnaryOperationWithBoxed(int opcode) {
return opcode == Opcodes.CHECKCAST || opcode == Opcodes.IFNULL;
protected void onMergeFail(@NotNull BoxedBasicValue value) {
}
}
@@ -22,12 +22,11 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.org.objectweb.asm.Opcodes;
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode;
import org.jetbrains.org.objectweb.asm.tree.InsnList;
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;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
class RedundantBoxingInterpreter extends BoxingInterpreter {
@@ -36,7 +35,8 @@ class RedundantBoxingInterpreter extends BoxingInterpreter {
static {
ImmutableSet.Builder<Integer> unsafeOperationsOpcodesBuilder = ImmutableSet.builder();
unsafeOperationsOpcodesBuilder.add(
Opcodes.AASTORE, Opcodes.PUTFIELD, Opcodes.PUTSTATIC, Opcodes.ARETURN
Opcodes.AASTORE, Opcodes.PUTFIELD, Opcodes.PUTSTATIC, Opcodes.ARETURN,
Opcodes.IFNONNULL, Opcodes.IFNULL, Opcodes.IF_ACMPEQ, Opcodes.IF_ACMPNE
);
unsafeOperationsOpcodes = unsafeOperationsOpcodesBuilder.build();
@@ -48,9 +48,24 @@ class RedundantBoxingInterpreter extends BoxingInterpreter {
super(insnList);
}
private void checkIfUnsafeOperationWithBoxed(@Nullable BasicValue value, @NotNull AbstractInsnNode insnNode) {
if (value instanceof BoxedBasicValue && unsafeOperationsOpcodes.contains(insnNode.getOpcode())) {
markAsDirty((BoxedBasicValue) value);
private static void markValueAsDirty(@NotNull BoxedBasicValue value) {
value.propagateRemovingAsUnsafe();
}
private static void addAssociatedInsn(@NotNull BoxedBasicValue value, @NotNull AbstractInsnNode insn) {
if (value.isSafeToRemove()) {
value.addInsn(insn);
}
}
private static void processOperationWithBoxedValue(@Nullable BasicValue value, @NotNull AbstractInsnNode insnNode) {
if (value instanceof BoxedBasicValue) {
if (unsafeOperationsOpcodes.contains(insnNode.getOpcode())) {
markValueAsDirty((BoxedBasicValue) value);
}
else {
addAssociatedInsn((BoxedBasicValue) value, insnNode);
}
}
}
@@ -61,7 +76,7 @@ class RedundantBoxingInterpreter extends BoxingInterpreter {
@NotNull BasicValue value2
) throws AnalyzerException {
checkIfUnsafeOperationWithBoxed(value2, insn);
processOperationWithBoxedValue(value2, insn);
return super.binaryOperation(insn, value1, value2);
}
@@ -72,57 +87,59 @@ class RedundantBoxingInterpreter extends BoxingInterpreter {
@NotNull BasicValue value1, @NotNull BasicValue value2, @NotNull BasicValue value3
) throws AnalyzerException {
checkIfUnsafeOperationWithBoxed(value3, insn);
processOperationWithBoxedValue(value3, insn);
return super.ternaryOperation(insn, value1, value2, value3);
}
@Nullable
@Override
protected boolean isAllowedUnaryOperationWithBoxed(int opcode) {
return opcode == Opcodes.CHECKCAST;
public BasicValue unaryOperation(
@NotNull AbstractInsnNode insn, @NotNull BasicValue value
) throws AnalyzerException {
processOperationWithBoxedValue(value, insn);
return super.unaryOperation(insn, value);
}
@Override
@NotNull
public BasicValue copyOperation(@NotNull AbstractInsnNode insn, @NotNull BasicValue value) throws AnalyzerException {
// currently we don't allow any copy operations with boxed values
if (value instanceof BoxedBasicValue) {
markAsDirty((BoxedBasicValue) value);
return new BasicValue(value.getType());
if (value instanceof BoxedBasicValue && insn.getOpcode() == Opcodes.ASTORE) {
((BoxedBasicValue) value).addVariableIndex(((VarInsnNode) insn).var);
}
processOperationWithBoxedValue(value, insn);
return super.copyOperation(insn, value);
}
@Override
protected void onNewBoxedValue(BoxedBasicValue value) {
protected void onNewBoxedValue(@NotNull BoxedBasicValue value) {
candidatesBoxedValues.add(value);
}
private void markAsDirty(BoxedBasicValue value) {
candidatesBoxedValues.remove(value);
@Override
protected void onUnboxing(
@NotNull BoxedBasicValue value, @NotNull AbstractInsnNode insn
) {
addAssociatedInsn(value, insn);
}
@Override
protected void onMethodCallWithBoxedValue(BoxedBasicValue value) {
markAsDirty(value);
protected void onMethodCallWithBoxedValue(@NotNull BoxedBasicValue value) {
markValueAsDirty(value);
}
@Override
protected void onMergeFail(BoxedBasicValue v) {
markAsDirty(v);
protected void onMergeFail(@NotNull BoxedBasicValue v) {
markValueAsDirty(v);
}
@NotNull
public List<BoxedBasicValue> getCandidatesBoxedValues() {
List<BoxedBasicValue> result = new ArrayList<BoxedBasicValue>();
for (BoxedBasicValue value : candidatesBoxedValues) {
if (value.wasUnboxed()) {
result.add(value);
}
}
return result;
public Set<BoxedBasicValue> getCandidatesBoxedValues() {
return candidatesBoxedValues;
}
}
@@ -16,14 +16,19 @@
package org.jetbrains.jet.codegen.optimization.boxing;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.codegen.optimization.transformer.MethodTransformer;
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode;
import org.jetbrains.org.objectweb.asm.tree.MethodNode;
import org.jetbrains.org.objectweb.asm.Opcodes;
import org.jetbrains.org.objectweb.asm.Type;
import org.jetbrains.org.objectweb.asm.tree.*;
import org.jetbrains.org.objectweb.asm.tree.analysis.Analyzer;
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue;
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame;
import java.util.List;
import java.util.HashSet;
import java.util.Set;
public class RedundantBoxingMethodTransformer extends MethodTransformer {
public RedundantBoxingMethodTransformer(MethodTransformer methodTransformer) {
@@ -32,21 +37,280 @@ public class RedundantBoxingMethodTransformer extends MethodTransformer {
@Override
public void transform(@NotNull String owner, @NotNull MethodNode node) {
for (BoxedBasicValue value : analyze(owner, node)) {
for (AbstractInsnNode insnNode : value.getAssociatedInsns()) {
node.instructions.remove(insnNode);
}
RedundantBoxingInterpreter interpreter = new RedundantBoxingInterpreter(node.instructions);
Analyzer<BasicValue> analyzer = new Analyzer<BasicValue>(interpreter);
Frame<BasicValue>[] frames = runAnalyzer(analyzer, owner, node);
Set<BoxedBasicValue> valuesToOptimize = filterSafeToRemoveValues(interpreter.getCandidatesBoxedValues());
if (valuesToOptimize.size() > 0) {
findValuesClashingWithVariables(node, frames);
valuesToOptimize = filterSafeToRemoveValues(valuesToOptimize);
adaptLocalVariableTableForBoxedValues(node, frames);
int[] remapping = variablesRemapping(valuesToOptimize, node);
applyVariablesRemapping(node, remapping);
adaptInstructionsForBoxedValues(node, frames, valuesToOptimize);
}
super.transform(owner, node);
}
private static List<BoxedBasicValue> analyze(@NotNull String owner, @NotNull MethodNode node) {
RedundantBoxingInterpreter interpreter = new RedundantBoxingInterpreter(node.instructions);
Analyzer<BasicValue> analyzer = new Analyzer<BasicValue>(interpreter);
@NotNull
private static Set<BoxedBasicValue> filterSafeToRemoveValues(@NotNull Set<BoxedBasicValue> values) {
return new HashSet<BoxedBasicValue>(Collections2.filter(values, new Predicate<BoxedBasicValue>() {
@Override
public boolean apply(BoxedBasicValue input) {
return input.isSafeToRemove();
}
}));
}
runAnalyzer(analyzer, owner, node);
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;
}
return interpreter.getCandidatesBoxedValues();
adaptLocalVariableTableEntryForBoxedValues(node, frames, localVariableNode);
}
}
private static void adaptLocalVariableTableEntryForBoxedValues(
@NotNull MethodNode node, @NotNull Frame<BasicValue>[] frames, @NotNull LocalVariableNode localVariableNode
) {
InsnList insnList = node.instructions;
int from = insnList.indexOf(localVariableNode.start) + 1;
int to = insnList.indexOf(localVariableNode.end) - 1;
for (int i = from; i <= to; i++) {
AbstractInsnNode insn = insnList.get(i);
if (insn.getOpcode() == Opcodes.ASTORE && ((VarInsnNode) insn).var == localVariableNode.index) {
if (frames[i] == null) {
return;
}
BasicValue top = frames[i].getStack(frames[i].getStackSize() - 1);
if (!(top instanceof BoxedBasicValue) || !((BoxedBasicValue) top).isSafeToRemove()) {
return;
}
localVariableNode.desc = ((BoxedBasicValue) top).getPrimitiveType().getDescriptor();
return;
}
}
}
private static void findValuesClashingWithVariables(
@NotNull MethodNode node, @NotNull Frame<BasicValue>[] frames
) {
while (findValuesClashingWithVariablesIteration(node, frames)) {
}
}
private static boolean findValuesClashingWithVariablesIteration(
@NotNull MethodNode node, @NotNull Frame<BasicValue>[] frames
) {
InsnList insnList = node.instructions;
boolean needToRepeat = false;
for (LocalVariableNode localVariableNode : node.localVariables) {
if (Type.getType(localVariableNode.desc).getSort() != Type.OBJECT) {
continue;
}
int index = localVariableNode.index;
int from = insnList.indexOf(localVariableNode.start) + 1;
int to = insnList.indexOf(localVariableNode.end) - 1;
if (isThereUnsafeStoreInstruction(insnList, frames, from, to, index)) {
needToRepeat |= markAllBoxedValuesStoredAsUnsafeToRemove(insnList, frames, from, to, index);
}
}
return needToRepeat;
}
/**
* Check if there are unsafe ASTORE instructions, that put into var something but boxed values of the same type
*
* @param insnList
* @param frames
* @param from
* @param to
* @param varIndex
* @return
*/
private static boolean isThereUnsafeStoreInstruction(
@NotNull InsnList insnList,
@NotNull Frame<BasicValue>[] frames,
int from, int to, int varIndex
) {
Type usedAsType = null;
for (int i = from; i <= to; i++) {
AbstractInsnNode insn = insnList.get(i);
if (insn.getOpcode() == Opcodes.ASTORE && ((VarInsnNode) insn).var == varIndex) {
if (frames[i] == null) {
return true;
}
BasicValue top = frames[i].getStack(frames[i].getStackSize() - 1);
if (!(top instanceof BoxedBasicValue) || !((BoxedBasicValue) top).isSafeToRemove()) {
return true;
}
if (usedAsType == null) {
usedAsType = ((BoxedBasicValue) top).getPrimitiveType();
}
if (!usedAsType.equals(((BoxedBasicValue) top).getPrimitiveType())) {
return true;
}
}
}
return false;
}
private static boolean markAllBoxedValuesStoredAsUnsafeToRemove(
@NotNull InsnList insnList,
@NotNull Frame<BasicValue>[] frames,
int from, int to, int varIndex
) {
boolean wasChanges = false;
for (int i = from; i <= to; i++) {
AbstractInsnNode insn = insnList.get(i);
if (insn.getOpcode() == Opcodes.ASTORE && ((VarInsnNode) insn).var == varIndex) {
if (frames[i] == null) {
continue;
}
BasicValue top = frames[i].getStack(frames[i].getStackSize() - 1);
if (!(top instanceof BoxedBasicValue) || !((BoxedBasicValue) top).isSafeToRemove()) {
continue;
}
wasChanges |= true;
((BoxedBasicValue) top).propagateRemovingAsUnsafe();
}
}
return wasChanges;
}
@NotNull
private static int[] variablesRemapping(@NotNull Iterable<BoxedBasicValue> values, @NotNull MethodNode node) {
Set<Integer> longTypesVars = new HashSet<Integer>();
for (BoxedBasicValue value : values) {
if (value.getPrimitiveType().getSize() == 2) {
longTypesVars.addAll(value.getVariablesIndexes());
}
}
node.maxLocals += longTypesVars.size();
int[] remapping = new int[node.maxLocals];
for (int i = 0; i < remapping.length; i++) {
remapping[i] = i;
}
for (int longVarIndex : longTypesVars) {
for (int i = longVarIndex + 1; i < remapping.length; i++) {
remapping[i]++;
}
}
return remapping;
}
private static void applyVariablesRemapping(@NotNull MethodNode node, @NotNull int[] remapping) {
for (AbstractInsnNode insn : node.instructions.toArray()) {
if (insn instanceof VarInsnNode) {
((VarInsnNode) insn).var = remapping[((VarInsnNode) insn).var];
}
if (insn instanceof IincInsnNode) {
((IincInsnNode) insn).var = remapping[((IincInsnNode) insn).var];
}
}
for (LocalVariableNode localVariableNode : node.localVariables) {
localVariableNode.index = remapping[localVariableNode.index];
}
}
private static void adaptInstructionsForBoxedValues(
@NotNull MethodNode node, @NotNull Frame<BasicValue>[] frames, @NotNull Set<BoxedBasicValue> values
) {
collectPopInstructionsForBoxedValues(node, frames);
for (BoxedBasicValue value : values) {
adaptInstructionsForBoxedValue(node, value);
}
}
private static void collectPopInstructionsForBoxedValues(
@NotNull MethodNode node, @NotNull Frame<BasicValue>[] frames
) {
for (int i = 0; i < node.instructions.size(); i++) {
AbstractInsnNode insn = node.instructions.get(i);
if (insn.getOpcode() != Opcodes.POP || frames[i] == null) {
continue;
}
BasicValue top = frames[i].getStack(frames[i].getStackSize() - 1);
if (top instanceof BoxedBasicValue && ((BoxedBasicValue) top).isSafeToRemove()) {
((BoxedBasicValue) top).addInsn(node.instructions.get(i));
}
}
}
private static void adaptInstructionsForBoxedValue(@NotNull MethodNode node, @NotNull BoxedBasicValue value) {
for (AbstractInsnNode insn : value.getAssociatedInsns()) {
adaptInstruction(node, insn, value);
}
}
private static void adaptInstruction(
@NotNull MethodNode node, @NotNull AbstractInsnNode insn, @NotNull BoxedBasicValue value
) {
boolean isDoubleSize = value.isDoubleSize();
switch (insn.getOpcode()) {
case Opcodes.POP:
if (isDoubleSize) {
node.instructions.set(
insn,
new InsnNode(Opcodes.POP2)
);
}
break;
case Opcodes.DUP:
if (isDoubleSize) {
node.instructions.set(
insn,
new 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.getPrimitiveType().getOpcode(intVarOpcode),
((VarInsnNode) insn).var
)
);
break;
default:
node.instructions.remove(insn);
}
}
}
@@ -28,6 +28,10 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue;
public class OptimizationBasicInterpreter extends BasicInterpreter {
private static final BasicValue MIXED_VALUE = new BasicValue(Type.getObjectType("#"));
private static final BasicValue BOOLEAN_VALUE = new BasicValue(Type.BOOLEAN_TYPE);
private static final BasicValue CHAR_VALUE = new BasicValue(Type.CHAR_TYPE);
private static final BasicValue BYTE_VALUE = new BasicValue(Type.BYTE_TYPE);
private static final BasicValue SHORT_VALUE = new BasicValue(Type.SHORT_TYPE);
@Override
@Nullable
@@ -35,11 +39,23 @@ public class OptimizationBasicInterpreter extends BasicInterpreter {
if (type == null) {
return super.newValue(null);
}
if (type.getSort() == Type.OBJECT) {
return new BasicValue(type);
}
return super.newValue(type);
switch (type.getSort()) {
case Type.VOID:
return null;
case Type.BOOLEAN:
return BOOLEAN_VALUE;
case Type.CHAR:
return CHAR_VALUE;
case Type.BYTE:
return BYTE_VALUE;
case Type.SHORT:
return SHORT_VALUE;
case Type.OBJECT:
return new BasicValue(type);
default:
return super.newValue(type);
}
}
@Override