blob: 5654bfc11a35e273daedc9d34483d2e0e53a79b3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.drill.exec.compile.bytecode;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import org.apache.drill.exec.util.AssertionUtil;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.analysis.BasicValue;
/**
* BasicValue with additional tracking information used to determine
* the replaceability of the value (a holder, or boxed value) for scalar replacement purposes.
*
* <p>Contains a set of flags that indicate how the value is used. These
* are updated throughout the life of the function, via the Analyzer/Interpreter,
* which simulate execution of the function. After the analysis is complete, the
* flags indicate how the value/variable was used, and that in turn indicates whether
* or not we can replace it.
*/
public class ReplacingBasicValue extends BasicValue {
private final ValueHolderIden iden; // identity of the holder this value represents
private final int index; // the original local variable slot this value/holder was assigned
/**
* The set of flags associated with this value.
*
* <p>This is factored out so that it can be shared among several values
* that are associated with each other.
*/
private static class FlagSet {
boolean isFunctionReturn = false;
boolean isFunctionArgument = false;
boolean isAssignedToMember = false;
boolean isAssignedInConditionalBlock = false;
boolean isThis = false;
/**
* Merge the given flag set into this one, creating a logical disjunction of the
* values.
*
* @param other the flag set to merge into this one
*/
public void mergeFrom(final FlagSet other) {
if (other.isFunctionReturn) {
isFunctionReturn = true;
}
if (other.isFunctionArgument) {
isFunctionArgument = true;
}
if (other.isAssignedToMember) {
isAssignedToMember = true;
}
if (other.isAssignedInConditionalBlock) {
isAssignedInConditionalBlock = true;
}
if (other.isThis) {
isThis = true;
}
}
/**
* Is the value with these flags replaceable?
*
* @return whether or not the value is replaceable
*/
public boolean isReplaceable() {
return !(isFunctionReturn || isFunctionArgument || isAssignedToMember || isAssignedInConditionalBlock || isThis);
}
/**
* Dump the flag set's values to a given StringBuilder. The content is added to
* a single line; no newlines are generated.
*
* <p>This is for logging and debugging purposes.
*
* @param sb the StringBuilder
*/
public void dump(final StringBuilder sb) {
boolean needSpace = false;
if (isFunctionReturn) {
sb.append("return");
needSpace = true;
}
if (isFunctionArgument) {
if (needSpace) {
sb.append(' ');
}
sb.append("argument");
needSpace = true;
}
if (isAssignedToMember) {
if (needSpace) {
sb.append(' ');
}
sb.append("member");
needSpace = true;
}
if (isAssignedInConditionalBlock) {
if (needSpace) {
sb.append(' ');
}
sb.append("conditional");
needSpace = true;
}
if (isThis) {
if (needSpace) {
sb.append(' ');
}
sb.append("this");
}
}
}
private FlagSet flagSet;
/**
* map of ReplacingBasicValue -> null; we need an IdentityHashSet, but there's no such thing;
* we just always set the value in the map to be null, and only care about the keys.
* Another solution might have been to define equals() and hashCode(), as apparently they are
* defined by BasicValue, but it's not clear what to do with some of the additional members here.
* It's not clear that not defining those won't also come back to bite us, depending on what ASM is
* doing with BasicValues under the covers.
*
* This value is null until we have our first associate, at which point this is allocated on demand.
*/
private IdentityHashMap<ReplacingBasicValue, ReplacingBasicValue> associates = null;
// TODO remove?
private HashSet<Integer> frameSlots = null; // slots in stack frame this has been assigned to
/**
* Create a new value representing a holder (boxed value).
*
* @param type the type of the holder
* @param iden the ValueHolderIden for the holder
* @param index the original local variable slot assigned to the value
* @param valueList TODO
* @return
*/
public static ReplacingBasicValue create(final Type type, final ValueHolderIden iden, final int index,
final List<ReplacingBasicValue> valueList) {
final ReplacingBasicValue replacingValue = new ReplacingBasicValue(type, iden, index);
valueList.add(replacingValue);
return replacingValue;
}
/**
* Add spaces to a StringBuilder.
*
* @param sb the StringBuilder
* @param indentLevel the number of spaces to add
*/
private static void indent(final StringBuilder sb, final int indentLevel) {
for(int i = 0; i < indentLevel; ++i) {
sb.append(' ');
}
}
/**
* Dump the value's members to a StringBuilder.
*
* <p>For logging and/or debugging.
*
* @param sb the StringBuilder
* @param indentLevel the amount of indentation (in spaces) to prepend to each line
*/
public void dump(final StringBuilder sb, final int indentLevel) {
indent(sb, indentLevel);
if (iden != null) {
iden.dump(sb);
} else {
sb.append("iden is null");
}
sb.append('\n');
indent(sb, indentLevel + 1);
sb.append(" index: " + index);
sb.append(' ');
flagSet.dump(sb);
sb.append(" frameSlots: ");
dumpFrameSlots(sb);
sb.append('\n');
if (associates != null) {
indent(sb, indentLevel);
sb.append("associates(index)");
for(ReplacingBasicValue value : associates.keySet()) {
sb.append(' ');
sb.append(value.index);
}
sb.append('\n');
}
}
private ReplacingBasicValue(final Type type, final ValueHolderIden iden, final int index) {
super(type);
this.iden = iden;
this.index = index;
flagSet = new FlagSet();
}
/**
* Indicates whether or not this value is replaceable.
*
* @return whether or not the value is replaceable
*/
public boolean isReplaceable() {
return flagSet.isReplaceable();
}
private void dumpFrameSlots(final StringBuilder sb) {
if (frameSlots != null) {
boolean first = true;
for(Integer i : frameSlots) {
if (!first) {
sb.append(' ');
}
first = false;
sb.append(i.intValue());
}
}
}
public void setFrameSlot(final int frameSlot) {
if (frameSlots == null) {
frameSlots = new HashSet<>();
}
frameSlots.add(frameSlot);
}
/**
* Add another ReplacingBasicValue with no associates to this value's set of
* associates.
*
* @param other the other value
*/
private void addOther(final ReplacingBasicValue other) {
assert other.associates == null;
associates.put(other, null);
other.associates = associates;
flagSet.mergeFrom(other.flagSet);
other.flagSet = flagSet;
}
/**
* Associate this value with another.
*
* <p>This value and/or the other may each already have their own set of
* associates.
*
* <p>Once associated, these values will share flag values, and a change to
* any one of them will be shared with all other members of the association.
*
* @param other the other value
*/
public void associate(final ReplacingBasicValue other) {
associate0(other);
if (AssertionUtil.ASSERT_ENABLED) {
assert associates == other.associates;
assert flagSet == other.flagSet;
if (associates != null) {
assert associates.containsKey(this);
assert associates.containsKey(other);
// check all the other values as well
for(ReplacingBasicValue value : associates.keySet()) {
assert associates.get(value) == null; // we never use the value
assert value.associates == associates;
assert value.flagSet == flagSet;
}
}
}
}
/**
* Does the real work of associate(), which is a convenient place to
* check all the assertions after this work is done.
*/
private void associate0(final ReplacingBasicValue other) {
// if it's the same value, there's nothing to do
if (this == other) {
return;
}
if (associates == null) {
if (other.associates != null) {
other.associate(this);
return;
}
// we have no associations so far, so start collecting them
associates = new IdentityHashMap<>();
associates.put(this, null);
addOther(other);
return;
}
// if we got here, we have associates
if (other.associates == null) {
addOther(other);
return;
}
// if we're already associated, there's nothing to do
if (associates.containsKey(other)) {
return;
}
// this and other both have disjoint associates; we need to merge them
IdentityHashMap<ReplacingBasicValue, ReplacingBasicValue> largerSet = associates;
FlagSet largerFlags = flagSet;
IdentityHashMap<ReplacingBasicValue, ReplacingBasicValue> smallerSet = other.associates;
FlagSet smallerFlags = other.flagSet;
// if necessary, swap them, so the larger/smaller labels are correct
if (largerSet.size() < smallerSet.size()) {
final IdentityHashMap<ReplacingBasicValue, ReplacingBasicValue> tempSet = largerSet;
largerSet = smallerSet;
smallerSet = tempSet;
final FlagSet tempFlags = largerFlags;
largerFlags = smallerFlags;
smallerFlags = tempFlags;
}
largerFlags.mergeFrom(smallerFlags);
final int largerSize = largerSet.size();
for(ReplacingBasicValue value : smallerSet.keySet()) {
value.flagSet = largerFlags;
value.associates = largerSet;
largerSet.put(value, null);
}
assert associates == largerSet;
assert largerSet.size() == largerSize + smallerSet.size();
assert flagSet == largerFlags;
assert largerSet.containsKey(this);
assert other.associates == largerSet;
assert other.flagSet == largerFlags;
assert largerSet.containsKey(other);
}
/**
* Mark this value as being used as a function return value.
*/
public void markFunctionReturn() {
flagSet.isFunctionReturn = true;
}
/**
* Clear the indication that this value is used as a function return value.
*/
public void disableFunctionReturn() {
flagSet.isFunctionReturn = false;
}
/**
* Indicates whether or not this value is used as a function return value.
*
* @return whether or not this value is used as a function return value
*/
public boolean isFunctionReturn() {
return flagSet.isFunctionReturn;
}
/**
* Mark this value as being used as a function argument.
*/
public void setFunctionArgument() {
flagSet.isFunctionArgument = true;
}
/**
* Indicates whether or not this value is used as a function argument.
*
* @return whether or not this value is used as a function argument
*/
public boolean isFunctionArgument() {
return flagSet.isFunctionArgument;
}
/**
* Mark this value as being assigned to a class member variable.
*/
public void setAssignedToMember() {
flagSet.isAssignedToMember = true;
}
/**
* Mark this value as being assigned to a variable inside of conditional block.
*/
public void setAssignedInConditionalBlock() {
flagSet.isAssignedInConditionalBlock = true;
}
/**
* Indicates whether or not this value is assigned to a class member variable.
*
* @return whether or not this value is assigned to a class member variable
*/
public boolean isAssignedToMember() {
return flagSet.isAssignedToMember;
}
/**
* Indicates whether or not this value is assigned to a variable inside of conditional block.
*
* @return whether or not this value is assigned to a variable inside of conditional block
*/
public boolean isAssignedInConditionalBlock() {
return flagSet.isAssignedInConditionalBlock;
}
/**
* Return the ValueHolder identity for this value.
*
* @return the ValueHolderIden for this value
*/
public ValueHolderIden getIden() {
return iden;
}
/**
* Get the original local variable slot assigned to this value/holder.
*
* @return the original local variable slot assigned to this value/holder
*/
public int getIndex() {
return index;
}
/**
* Mark this value as a "this" reference.
*/
public void setThis() {
flagSet.isThis = true;
}
/**
* Indicates whether or not this value is a "this" reference.
*
* @return whether or not this value is a "this" reference
*/
public boolean isThis() {
return flagSet.isThis;
}
}