/*
* Bytecode Analysis Framework
* Copyright (C) 2003,2004 University of Maryland
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.umd.cs.findbugs.ba.vna;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.FieldSummary;
import edu.umd.cs.findbugs.ba.Frame;
import edu.umd.cs.findbugs.ba.XField;
import edu.umd.cs.findbugs.util.Strings;
import edu.umd.cs.findbugs.util.Util;
/**
* A dataflow value representing a Java stack frame with value number
* information.
*
* @author David Hovemeyer
* @see ValueNumber
* @see ValueNumberAnalysis
*/
public class ValueNumberFrame extends Frame<ValueNumber> implements ValueNumberAnalysisFeatures {
private ArrayList<ValueNumber> mergedValueList;
private Map<AvailableLoad, ValueNumber[]> availableLoadMap;
private Map<AvailableLoad,ValueNumber> mergedLoads ;
private Map<ValueNumber, AvailableLoad> previouslyKnownAs;
public boolean phiNodeForLoads;
public ValueNumberFrame(int numLocals) {
super(numLocals);
if (REDUNDANT_LOAD_ELIMINATION) {
setAvailableLoadMap(Collections.<AvailableLoad, ValueNumber[]>emptyMap());
setMergedLoads(Collections.<AvailableLoad, ValueNumber>emptyMap());
setPreviouslyKnownAs(Collections.<ValueNumber, AvailableLoad>emptyMap());
}
}
public String availableLoadMapAsString() {
StringBuilder buf = new StringBuilder("{ ");
for(Map.Entry<AvailableLoad, ValueNumber[]> e : getAvailableLoadMap().entrySet()) {
buf.append(e.getKey());
buf.append("=");
for(ValueNumber v : e.getValue())
buf.append(v).append(",");
buf.append("; ");
}
buf.append(" }");
return buf.toString();
}
public @CheckForNull AvailableLoad getLoad(ValueNumber v) {
if (!REDUNDANT_LOAD_ELIMINATION)
return null;
for(Map.Entry<AvailableLoad, ValueNumber[]> e : getAvailableLoadMap().entrySet()) {
ValueNumber[] values = e.getValue();
if (values != null)
for(ValueNumber v2 : values)
if (v.equals(v2))
return e.getKey();
}
return null;
}
/**
* Look for an available load.
*
* @param availableLoad the AvailableLoad (reference and field)
* @return the value(s) available, or null if no matching entry is found
*/
public ValueNumber[] getAvailableLoad(AvailableLoad availableLoad) {
return getAvailableLoadMap().get(availableLoad);
}
/**
* Add an available load.
*
* @param availableLoad the AvailableLoad (reference and field)
* @param value the value(s) loaded
*/
public void addAvailableLoad(AvailableLoad availableLoad, @NonNull ValueNumber[] value) {
if (value == null) throw new IllegalStateException();
getUpdateableAvailableLoadMap().put(availableLoad, value);
for(ValueNumber v : value) {
getUpdateablePreviouslyKnownAs().put(v, availableLoad);
if (RLE_DEBUG) {
System.out.println("Adding available load of " + availableLoad + " for " + v + " to " + System.identityHashCode(this));
}
}
}
private static <K,V> void removeAllKeys(Map<K,V> map, Iterable<K> removeMe) {
for(K k : removeMe)
map.remove(k);
}
/**
* Kill all loads of given field.
*
* @param field the field
*/
public void killLoadsOfField(XField field) {
if (!REDUNDANT_LOAD_ELIMINATION) return;
HashSet<AvailableLoad> killMe = new HashSet<AvailableLoad>();
for(AvailableLoad availableLoad : getAvailableLoadMap().keySet()) {
if (availableLoad.getField().equals(field)) {
if (RLE_DEBUG)
System.out.println("KILLING Load of " + availableLoad + " in " + this);
killMe.add(availableLoad);
}
}
killAvailableLoads(killMe);
}
private static boolean USE_WRITTEN_OUTSIDE_OF_CONSTRUCTOR = true;
/**
* Kill all loads.
* This conservatively handles method calls where we
* don't really know what fields might be assigned.
*/
public void killAllLoads() {
if (!REDUNDANT_LOAD_ELIMINATION) return;
FieldSummary fieldSummary = AnalysisContext.currentAnalysisContext().getFieldSummary();
HashSet<AvailableLoad> killMe = new HashSet<AvailableLoad>();
for(AvailableLoad availableLoad : getAvailableLoadMap().keySet()) {
XField field = availableLoad.getField();
if (field.isVolatile() || !field.isFinal() && (!USE_WRITTEN_OUTSIDE_OF_CONSTRUCTOR || fieldSummary.isWrittenOutsideOfConstructor(field))) {
if (RLE_DEBUG)
System.out.println("KILLING load of " + availableLoad + " in " + this);
killMe.add(availableLoad);
}
}
killAvailableLoads(killMe);
}
public void killAllLoadsExceptFor(@CheckForNull ValueNumber v) {
if (!REDUNDANT_LOAD_ELIMINATION) return;
AvailableLoad myLoad = getLoad(v);
HashSet<AvailableLoad> killMe = new HashSet<AvailableLoad>();
for(AvailableLoad availableLoad : getAvailableLoadMap().keySet()) {
if (!availableLoad.getField().isFinal() && !availableLoad.equals(myLoad)) {
if (RLE_DEBUG)
System.out.println("KILLING load of " + availableLoad + " in " + this);
killMe.add(availableLoad);
}
}
killAvailableLoads(killMe);
}
/**
* Kill all loads.
* This conservatively handles method calls where we
* don't really know what fields might be assigned.
*/
public void killAllLoadsOf(@CheckForNull ValueNumber v) {
if (!REDUNDANT_LOAD_ELIMINATION) return;
FieldSummary fieldSummary = AnalysisContext.currentAnalysisContext().getFieldSummary();
HashSet<AvailableLoad> killMe = new HashSet<AvailableLoad>();
for(AvailableLoad availableLoad : getAvailableLoadMap().keySet()) {
if (availableLoad.getReference() != v) continue;
XField field = availableLoad.getField();
if (!field.isFinal() && (!USE_WRITTEN_OUTSIDE_OF_CONSTRUCTOR || fieldSummary.isWrittenOutsideOfConstructor(field))) {
if (RLE_DEBUG) System.out.println("Killing load of " + availableLoad + " in " + this);
killMe.add(availableLoad);
}
}
killAvailableLoads(killMe);
}
public void killLoadsOf(Set<XField> fieldsToKill) {
if (!REDUNDANT_LOAD_ELIMINATION) return;
HashSet<AvailableLoad> killMe = new HashSet<AvailableLoad>();
for(AvailableLoad availableLoad : getAvailableLoadMap().keySet()) {
if (fieldsToKill.contains(availableLoad.getField()) )
killMe.add(availableLoad);
}
killAvailableLoads(killMe);
}
public void killLoadsWithSimilarName(String className, String methodName) {
if (!REDUNDANT_LOAD_ELIMINATION) return;
String packageName = extractPackageName(className);
HashSet<AvailableLoad> killMe = new HashSet<AvailableLoad>();
for(AvailableLoad availableLoad : getAvailableLoadMap().keySet()) {
XField field = availableLoad.getField();
String fieldPackageName = extractPackageName(field.getClassName());
if (packageName.equals(fieldPackageName) && field.isStatic()
&& methodName.toLowerCase().indexOf(field.getName().toLowerCase()) >= 0)
killMe.add(availableLoad);
}
killAvailableLoads(killMe);
}
/**
* @param killMe
*/
private void killAvailableLoads(HashSet<AvailableLoad> killMe) {
if (killMe.size() > 0)
removeAllKeys(getUpdateableAvailableLoadMap(), killMe);
}
/**
* @param className
* @return
*/
private String extractPackageName(String className) {
return className.substring(className.lastIndexOf('.')+1);
}
void mergeAvailableLoadSets(ValueNumberFrame other, ValueNumberFactory factory, MergeTree mergeTree) {
if (REDUNDANT_LOAD_ELIMINATION) {
// Merge available load sets.
// Only loads that are available in both frames
// remain available. All others are discarded.
String s = "";
if (RLE_DEBUG) {
s = "Merging " + this.availableLoadMapAsString() + " and " + other.availableLoadMapAsString();
}
boolean changed = false;
if (other.isBottom()) {
changed = !this.getAvailableLoadMap().isEmpty();
setAvailableLoadMap(Collections.<AvailableLoad, ValueNumber[]>emptyMap());
}
else if (!other.isTop()) {
for(Map.Entry<AvailableLoad,ValueNumber[]> e : getUpdateableAvailableLoadMap().entrySet()) {
AvailableLoad load = e.getKey();
ValueNumber[] myVN = e.getValue();
ValueNumber[] otherVN = other.getAvailableLoadMap().get(load);
if (false && this.phiNodeForLoads && myVN != null && myVN.length == 1 && myVN[0].hasFlag(ValueNumber.PHI_NODE))
continue;
if (!Arrays.equals(myVN, otherVN)) {
ValueNumber phi = getMergedLoads().get(load);
if (phi == null) {
int flags = ValueNumber.PHI_NODE;
for(ValueNumber vn : myVN) {
flags |= vn.getFlags();
}
if (otherVN != null) for(ValueNumber vn : otherVN) {
flags |= vn.getFlags();
}
phi = factory.createFreshValue(flags);
getUpdateableMergedLoads().put(load, phi);
for(ValueNumber vn : myVN) {
mergeTree.mapInputToOutput(vn, phi);
}
if (otherVN != null) for(ValueNumber vn : otherVN) {
mergeTree.mapInputToOutput(vn, phi);
}
if (RLE_DEBUG)
System.out.println("Creating phi node " + phi + " for " + load + " from " + Strings.toString(myVN) + " x " + Strings.toString(otherVN) + " in " + System.identityHashCode(this));
changed = true;
e.setValue(new ValueNumber[] { phi });
} else {
if (RLE_DEBUG)
System.out.println("Reusing phi node : " + phi + " for " + load + " from "+ Strings.toString(myVN) + " x " + Strings.toString(otherVN)+ " in " + System.identityHashCode(this));
if (myVN.length != 1 || !myVN[0].equals(phi))
e.setValue(new ValueNumber[] { phi });
}
}
}
}
Map<ValueNumber, AvailableLoad> previouslyKnownAsOther = other.getPreviouslyKnownAs();
if (getPreviouslyKnownAs() != previouslyKnownAsOther
&& previouslyKnownAsOther.size() != 0) {
if (getPreviouslyKnownAs().size() == 0)
assignPreviouslyKnownAs(other);
else getUpdateablePreviouslyKnownAs().putAll(previouslyKnownAsOther);
}
if (changed)
this.phiNodeForLoads = true;
if (changed && RLE_DEBUG) {
System.out.println(s);
System.out.println(" Result is " + this.availableLoadMapAsString());
System.out.println(" Set phi for " + System.identityHashCode(this));
}
}
}
ValueNumber getMergedValue(int slot) {
return mergedValueList.get(slot);
}
void setMergedValue(int slot, ValueNumber value) {
mergedValueList.set(slot, value);
}
@Override
public void copyFrom(Frame<ValueNumber> other) {
if (!(other instanceof ValueNumberFrame)) throw new IllegalArgumentException();
// If merged value list hasn't been created yet, create it.
if (mergedValueList == null && other.isValid()) {
// This is where this frame gets its size.
// It will have the same size as long as it remains valid.
mergedValueList = new ArrayList<ValueNumber>();
int numSlots = other.getNumSlots();
for (int i = 0; i < numSlots; ++i)
mergedValueList.add(null);
}
if (REDUNDANT_LOAD_ELIMINATION) {
assignAvailableLoadMap((ValueNumberFrame) other);
assignPreviouslyKnownAs((ValueNumberFrame) other);
}
super.copyFrom(other);
}
private void assignAvailableLoadMap(ValueNumberFrame other) {
Map<AvailableLoad, ValueNumber[]> availableLoadMapOther = other.getAvailableLoadMap();
if (availableLoadMapOther instanceof HashMap) {
availableLoadMapOther = Collections.<AvailableLoad, ValueNumber[]>unmodifiableMap(availableLoadMapOther);
other.setAvailableLoadMap(availableLoadMapOther);
setAvailableLoadMap(availableLoadMapOther);
constructedUnmodifiableMap++;
} else {
setAvailableLoadMap(availableLoadMapOther);
reusedMap++;
}
}
private void assignPreviouslyKnownAs(ValueNumberFrame other) {
Map<ValueNumber, AvailableLoad> previouslyKnownAsOther = other.getPreviouslyKnownAs();
if (previouslyKnownAsOther instanceof HashMap) {
previouslyKnownAsOther = Collections.<ValueNumber, AvailableLoad>unmodifiableMap(previouslyKnownAsOther);
other.setPreviouslyKnownAs(previouslyKnownAsOther);
setPreviouslyKnownAs(previouslyKnownAsOther);
constructedUnmodifiableMap++;
} else {
setPreviouslyKnownAs(previouslyKnownAsOther);
reusedMap++;
}
}
static int constructedUnmodifiableMap;
static int reusedMap;
@Override
public String toString() {
String frameValues = super.toString();
if (RLE_DEBUG) {
StringBuilder buf = new StringBuilder();
buf.append(frameValues);
Iterator<AvailableLoad> i = getAvailableLoadMap().keySet().iterator();
boolean first = true;
while (i.hasNext()) {
AvailableLoad key = i.next();
ValueNumber[] value = getAvailableLoadMap().get(key);
if (first)
first = false;
else
buf.append(',');
buf.append(key + "=" + valueToString(value));
}
buf.append(" #");
buf.append(System.identityHashCode(this));
if (phiNodeForLoads) buf.append(" phi");
return buf.toString();
} else {
return frameValues;
}
}
private static String valueToString(ValueNumber[] valueNumberList) {
StringBuilder buf = new StringBuilder();
buf.append('[');
boolean first = true;
for (ValueNumber aValueNumberList : valueNumberList) {
if (first)
first = false;
else
buf.append(',');
buf.append(aValueNumberList.getNumber());
}
buf.append(']');
return buf.toString();
}
public boolean fuzzyMatch(ValueNumber v1, ValueNumber v2) {
if (REDUNDANT_LOAD_ELIMINATION)
return v1.equals(v2) || fromMatchingLoads(v1, v2) || haveMatchingFlags(v1, v2);
else
return v1.equals(v2);
}
public boolean veryFuzzyMatch(ValueNumber v1, ValueNumber v2) {
if (REDUNDANT_LOAD_ELIMINATION)
return v1.equals(v2) || fromMatchingFields(v1, v2) || haveMatchingFlags(v1, v2);
else
return v1.equals(v2);
}
public boolean fromMatchingLoads(ValueNumber v1, ValueNumber v2) {
AvailableLoad load1 = getLoad(v1);
if (load1 == null) load1 = getPreviouslyKnownAs().get(v1);
if (load1 == null) return false;
AvailableLoad load2 = getLoad(v2);
if (load2 == null) load2 = getPreviouslyKnownAs().get(v2);
if (load2 == null) return false;
return load1.equals(load2);
}
public boolean fromMatchingFields(ValueNumber v1, ValueNumber v2) {
AvailableLoad load1 = getLoad(v1);
if (load1 == null) load1 = getPreviouslyKnownAs().get(v1);
if (load1 == null) return false;
AvailableLoad load2 = getLoad(v2);
if (load2 == null) load2 = getPreviouslyKnownAs().get(v2);
if (load2 == null) return false;
if (load1.equals(load2)) return true;
if (load1.getField().equals(load2.getField())) {
ValueNumber source1 = load1.getReference();
ValueNumber source2 = load2.getReference();
if (!this.contains(source1)) return true;
if (!this.contains(source2)) return true;
}
return false;
}
/**
* @param v1
* @param v2
* @return true if v1 and v2 have a flag in common
*/
public boolean haveMatchingFlags(ValueNumber v1, ValueNumber v2) {
int flag1 = v1.getFlags();
int flag2 = v2.getFlags();
return (flag1 & flag2) != 0;
}
public Collection<ValueNumber> valueNumbersForLoads() {
HashSet<ValueNumber> result = new HashSet<ValueNumber>();
if (REDUNDANT_LOAD_ELIMINATION)
for(Map.Entry<AvailableLoad, ValueNumber[]> e : getAvailableLoadMap().entrySet()) {
if (e.getValue() != null)
for(ValueNumber v2 : e.getValue())
result.add(v2);
}
return result;
}
/**
* @param availableLoadMap The availableLoadMap to set.
*/
private void setAvailableLoadMap(Map<AvailableLoad, ValueNumber[]> availableLoadMap) {
this.availableLoadMap = availableLoadMap;
}
/**
* @return Returns the availableLoadMap.
*/
private Map<AvailableLoad, ValueNumber[]> getAvailableLoadMap() {
return availableLoadMap;
}
private Map<AvailableLoad, ValueNumber[]> getUpdateableAvailableLoadMap() {
if (!(availableLoadMap instanceof HashMap)) {
HashMap<AvailableLoad, ValueNumber[]> tmp = new HashMap<AvailableLoad, ValueNumber[]>(availableLoadMap.size()+4);
tmp.putAll(availableLoadMap);
availableLoadMap = tmp;
}
return availableLoadMap;
}
/**
* @param mergedLoads The mergedLoads to set.
*/
private void setMergedLoads(Map<AvailableLoad,ValueNumber> mergedLoads) {
this.mergedLoads = mergedLoads;
}
/**
* @return Returns the mergedLoads.
*/
private Map<AvailableLoad,ValueNumber> getMergedLoads() {
return mergedLoads;
}
private Map<AvailableLoad,ValueNumber> getUpdateableMergedLoads() {
if (!(mergedLoads instanceof HashMap))
mergedLoads = new HashMap<AvailableLoad, ValueNumber>();
return mergedLoads;
}
/**
* @param previouslyKnownAs The previouslyKnownAs to set.
*/
private void setPreviouslyKnownAs(Map<ValueNumber, AvailableLoad> previouslyKnownAs) {
this.previouslyKnownAs = previouslyKnownAs;
}
/**
* @return Returns the previouslyKnownAs.
*/
private Map<ValueNumber, AvailableLoad> getPreviouslyKnownAs() {
return previouslyKnownAs;
}
static int createdEmptyMap;
static int madeImmutableMutable;
static int reusedMutableMap;
static {
Util.runLogAtShutdown(new Runnable() {
public void run() {
System.err.println("Getting updatable previously known as:");
System.err.println(" " + createdEmptyMap + " created empty map");
System.err.println(" " + madeImmutableMutable + " made immutable map mutable");
System.err.println(" " + reusedMutableMap + " reused mutable map");
System.err.println("Copying map:");
System.err.println(" " + constructedUnmodifiableMap + " made mutable map unmodifiable");
System.err.println(" " + reusedMap + " reused immutable map");
System.err.println();
}});
}
private Map<ValueNumber, AvailableLoad> getUpdateablePreviouslyKnownAs() {
if (previouslyKnownAs.size() == 0) {
previouslyKnownAs = new HashMap<ValueNumber, AvailableLoad>(4);
createdEmptyMap++;
}
else if (!(previouslyKnownAs instanceof HashMap)) {
HashMap<ValueNumber, AvailableLoad> tmp = new HashMap<ValueNumber, AvailableLoad>(previouslyKnownAs.size()+4);
tmp.putAll(previouslyKnownAs);
previouslyKnownAs = tmp;
madeImmutableMutable++;
} else
reusedMutableMap++;
return previouslyKnownAs;
}
}
// vim:ts=4