| /* |
| * 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; |
| } |
| } |