/*
* This file is part of JOP, the Java Optimized Processor
* see <http://www.jopdesign.com/>
*
* Copyright (C) 2011, Stefan Hepp (stefan@stefant.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jopdesign.jcopter.analysis;
import com.jopdesign.common.AppInfo;
import com.jopdesign.common.MethodInfo;
import com.jopdesign.common.code.CallGraph;
import com.jopdesign.common.code.CallGraph.ContextEdge;
import com.jopdesign.common.code.ControlFlowGraph.InvokeNode;
import com.jopdesign.common.code.ExecutionContext;
import com.jopdesign.common.code.InvokeSite;
import com.jopdesign.common.graphutils.DFSTraverser;
import com.jopdesign.common.graphutils.DFSTraverser.DFSEdgeType;
import com.jopdesign.common.graphutils.DFSTraverser.DFSVisitor;
import com.jopdesign.common.graphutils.DFSTraverser.EmptyDFSVisitor;
import com.jopdesign.common.graphutils.GraphUtils;
import com.jopdesign.common.misc.AppInfoError;
import com.jopdesign.common.misc.MiscUtils;
import com.jopdesign.jcopter.JCopter;
import com.jopdesign.wcet.WCETTool;
import com.jopdesign.wcet.analysis.AnalysisContextLocal;
import com.jopdesign.wcet.analysis.GlobalAnalysis;
import com.jopdesign.wcet.analysis.LocalAnalysis;
import com.jopdesign.wcet.analysis.RecursiveAnalysis;
import com.jopdesign.wcet.analysis.RecursiveAnalysis.RecursiveStrategy;
import com.jopdesign.wcet.analysis.WcetCost;
import com.jopdesign.wcet.ipet.IPETConfig;
import com.jopdesign.wcet.ipet.IPETConfig.CacheCostCalculationMethod;
import com.jopdesign.wcet.jop.MethodCache;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.log4j.Logger;
import org.jgrapht.alg.TransitiveClosure;
import org.jgrapht.graph.SimpleDirectedGraph;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
/**
* This analysis keeps track of the number of bytes and blocks of code reachable from a method and
* provides estimations of cache miss numbers.
*
* TODO create subclasses for various analysis types instead of enum
*
* @author Stefan Hepp (stefan@stefant.org)
*/
public class MethodCacheAnalysis {
public enum AnalysisType { ALWAYS_MISS, ALWAYS_HIT, ALWAYS_MISS_OR_HIT, ALL_FIT_REGIONS }
private static final Logger logger = Logger.getLogger(JCopter.LOG_ANALYSIS+".MethodCacheAnalysis");
private final JCopter jcopter;
private final MethodCache cache;
private final CallGraph callGraph;
private final Map<ExecutionContext,Integer> cacheBlocks;
private final Map<ExecutionContext, Set<MethodInfo>> reachableMethods;
private final AnalysisType analysisType;
private final Set<MethodInfo> classifyChanges;
///////////////////////////////////////////////////////////////////////////////////
// Constructors, initialization, standard getter
///////////////////////////////////////////////////////////////////////////////////
public MethodCacheAnalysis(JCopter jcopter, AnalysisType analysisType, CallGraph callGraph) {
this.jcopter = jcopter;
this.analysisType = analysisType;
this.cache = jcopter.getMethodCache();
this.callGraph = callGraph;
cacheBlocks = new LinkedHashMap<ExecutionContext, Integer>(callGraph.getNodes().size());
reachableMethods = new LinkedHashMap<ExecutionContext, Set<MethodInfo>>(callGraph.getNodes().size());
classifyChanges = new LinkedHashSet<MethodInfo>();
}
public CallGraph getCallGraph() {
return callGraph;
}
public void initialize() {
cacheBlocks.clear();
reachableMethods.clear();
if (analysisType == AnalysisType.ALWAYS_HIT || analysisType == AnalysisType.ALWAYS_MISS) {
// we do not use the number of blocks for those analyses
return;
}
// we add the method to the reachable set anyway, so we can ignore self-loops
SimpleDirectedGraph<ExecutionContext,ContextEdge> closure = GraphUtils.createSimpleGraph(callGraph.getGraph());
TransitiveClosure.INSTANCE.closeSimpleDirectedGraph(closure);
updateNodes(closure, closure.vertexSet(), false);
}
///////////////////////////////////////////////////////////////////////////////////
// Query analysis results
///////////////////////////////////////////////////////////////////////////////////
public boolean allFit(MethodInfo method) {
for (ExecutionContext node : callGraph.getNodes(method)) {
if (!allFit(node)) return false;
}
return true;
}
public boolean allFit(ExecutionContext node) {
Integer blocks = cacheBlocks.get(node);
if (blocks == null) {
// not a node in the graph .. over-approximate simply by checking all nodes
// do not call allFit(MethodInfo) or we might get endless recursion if for some reason the analysis
// has not been updated for this method
for (ExecutionContext n : callGraph.getNodes(node.getMethodInfo())) {
blocks = cacheBlocks.get(n);
if (blocks == null) {
logger.warn("No analysis results for method "+node.getMethodInfo());
return false;
}
if (!cache.allFit(blocks)) return false;
}
return true;
}
return cache.allFit(blocks);
}
public long getInvokeReturnCacheCosts(ExecFrequencyProvider ecp, InvokeSite invokeSite) {
if (analysisType == AnalysisType.ALWAYS_HIT) return 0;
AppInfo appInfo = AppInfo.getSingleton();
int size = 0;
for (MethodInfo method : appInfo.findImplementations(invokeSite)) {
size = Math.max(size, getMethodSize(method));
}
size = MiscUtils.bytesToWords(size);
int sizeInvoker = getMethodSize(invokeSite.getInvoker());
sizeInvoker = MiscUtils.bytesToWords(sizeInvoker);
long invokeCosts = cache.getMissPenaltyOnInvoke(size, invokeSite.getInvokeInstruction());
long returnCosts = cache.getMissPenaltyOnReturn(sizeInvoker,invokeSite.getInvokeeRef().getDescriptor().getType());
return getInvokeReturnCacheCosts(ecp, invokeSite, invokeCosts, returnCosts);
}
/**
* @param ecp exec count provider
* @param invokeSite the invoke site to check
* @param invokeCacheCosts additional cycles for a single cache miss on invoke
* @param returnCacheCosts additional cycles for a single cache miss on return
* @return the number of cache miss cycles for all executions of the invoke
*/
public long getInvokeReturnCacheCosts(ExecFrequencyProvider ecp, InvokeSite invokeSite,
long invokeCacheCosts, long returnCacheCosts)
{
if (analysisType == AnalysisType.ALWAYS_HIT) return 0;
if (analysisType == AnalysisType.ALWAYS_MISS || !allFit(invokeSite.getInvoker())) {
// outside all-fit, every invoke is a miss
long count = ecp.getExecCount(invokeSite);
return count * (invokeCacheCosts + returnCacheCosts);
}
if (analysisType == AnalysisType.ALWAYS_MISS_OR_HIT) {
return 0;
}
// invoke cache misses
long misses = getPersistentMisses(ecp, callGraph.getNodes(invokeSite.getInvoker()));
if (cache.isLRU()) {
// we only have cache costs at the invoke, we can assume the return is always a hit
return misses * invokeCacheCosts;
}
// we may have return cache misses too, over-approximate
return misses * (invokeCacheCosts + returnCacheCosts);
}
public long getInvokeMissCount(ExecFrequencyProvider ecp, InvokeSite invokeSite, MethodInfo invokee) {
if (analysisType == AnalysisType.ALWAYS_HIT) return 0;
if (analysisType == AnalysisType.ALWAYS_MISS || !allFit(invokeSite.getInvoker())) {
// outside all-fit, every invoke is a miss
return ecp.getExecCount(invokeSite, invokee);
}
if (analysisType == AnalysisType.ALWAYS_MISS_OR_HIT || cache.isLRU()) {
return 0;
}
return getPersistentMisses(ecp, callGraph.getNodes(invokeSite.getInvoker()));
}
public long getInvokeMissCount(ExecFrequencyProvider ecp, MethodInfo invokee) {
long count = 0;
// get all invoke sites of the invokee
for (InvokeSite invokeSite : callGraph.getInvokeSites(invokee)) {
count += getInvokeMissCount(ecp, invokeSite, invokee);
}
return count;
}
/**
* @param ecp exec count provider
* @param context the method to calculate return cache misses for, and a context in which the method is invoked.
* The cache missese are calculated only for the executions with the given context.
* @return the total cache misses for all returns to the given method
*/
public long getReturnMissCount(ExecFrequencyProvider ecp, ExecutionContext context) {
if (analysisType == AnalysisType.ALWAYS_HIT) return 0;
if (analysisType == AnalysisType.ALWAYS_MISS || !allFit(context.getMethodInfo())) {
// outside all-fit, every invoke returns with a miss
long retCount = 0;
for (InvokeSite invokeSite : context.getMethodInfo().getCode().getInvokeSites()) {
retCount += ecp.getExecFrequency(invokeSite);
}
return ecp.getExecCount(context) * retCount;
}
if (analysisType == AnalysisType.ALWAYS_MISS_OR_HIT || cache.isLRU()) {
return 0;
}
// we have at most one return miss of invokes in this method
return getPersistentMisses(ecp, callGraph.getNodes(context));
}
public long getReturnMissCount(ExecFrequencyProvider ecp, CodeModification modification) {
if (analysisType == AnalysisType.ALWAYS_HIT) return 0;
if (analysisType == AnalysisType.ALWAYS_MISS || !allFit(modification.getMethod())) {
// outside all-fit, every invoke returns with a miss
long retCount = 0;
for (InvokeSite invokeSite : modification.getMethod().getCode().getInvokeSites()) {
InstructionHandle ih = invokeSite.getInstructionHandle();
if (modification.getStart().getPosition() <= ih.getPosition() &&
modification.getEnd().getPosition() >= ih.getPosition())
{
continue;
}
retCount += ecp.getExecFrequency(invokeSite);
}
return ecp.getExecCount(modification.getMethod()) * retCount;
}
if (analysisType == AnalysisType.ALWAYS_MISS_OR_HIT || cache.isLRU()) {
return 0;
}
// we have at most one return miss of invokes in this method
return getPersistentMisses(ecp, callGraph.getNodes(modification.getMethod()));
}
/**
* @param ecp execution counts to use
* @param modification the modifications which will be done
* @return the number of cache miss cycles due to the code size change, excluding the effects on the modified code
*/
public long getDeltaCacheMissCosts(ExecFrequencyProvider ecp, CodeModification modification) {
if (analysisType == AnalysisType.ALWAYS_HIT) return 0;
int deltaBytes = modification.getDeltaLocalCodesize();
if (deltaBytes == 0) return 0;
MethodInfo method = modification.getMethod();
int size = getMethodSize(method);
int oldWords = MiscUtils.bytesToWords(size);
int newWords = MiscUtils.bytesToWords(size+deltaBytes);
int deltaBlocks = cache.requiredNumberOfBlocks(newWords) - cache.requiredNumberOfBlocks(oldWords);
//int newBlocks = getRequiredBlocks(method) + deltaBlocks;
// calc various cache miss cost deltas
long deltaInvokeCacheMissCosts = cache.getMissPenalty(newWords, true) -
cache.getMissPenalty(oldWords, true);
long deltaReturnCacheMissCosts = cache.getMissPenalty(newWords, false) -
cache.getMissPenalty(oldWords, false);
long costs = 0;
// we have cache costs due to invokes of the modified method
costs += getInvokeMissCount(ecp, modification.getMethod()) * deltaInvokeCacheMissCosts;
// .. and due to returns from invokees to the modified method
costs += getReturnMissCount(ecp, modification) * deltaReturnCacheMissCosts;
// .. and because other methods may not fit into the cache anymore
costs += getAllFitChangeCosts(ecp, modification, deltaBlocks);
return costs;
}
private long getAllFitChangeCosts(ExecFrequencyProvider ecp, CodeModification modification, int deltaBlocks)
{
if (analysisType == AnalysisType.ALWAYS_HIT || analysisType == AnalysisType.ALWAYS_MISS) {
return 0;
}
int deltaBytes = modification.getDeltaLocalCodesize();
MethodInfo method = modification.getMethod();
// for ALWAYS_MISS_HIT oder MOST_ONCE we need to find out what has changed for all-fit
Set<MethodInfo> changes = findClassificationChanges(method, deltaBlocks,
modification.getRemovedInvokees(), false);
AppInfo appInfo = AppInfo.getSingleton();
// In all nodes where we have changes, we need to sum up the new costs
long deltaCosts = 0;
for (MethodInfo node : changes) {
// we do not need to count the invokes of the method itself
// but all invokes in the method are now no longer always-hit/-miss
for (InvokeSite invokeSite : node.getCode().getInvokeSites()) {
// Note: this is very similar to getInvokeReturnCacheCosts(invokeSite), but we cannot use
// this here, because that method uses allFit and does not honor our 'virtual' codesize change
int size = 0;
for (MethodInfo impl : appInfo.findImplementations(invokeSite)) {
size = Math.max(size, getMethodSize(impl));
}
size = MiscUtils.bytesToWords(size);
int sizeInvoker = getMethodSize(invokeSite.getInvoker());
sizeInvoker = MiscUtils.bytesToWords(sizeInvoker);
long invokeCosts = cache.getMissPenaltyOnInvoke(size, invokeSite.getInvokeInstruction());
long returnCosts = cache.getMissPenaltyOnReturn(sizeInvoker,invokeSite.getInvokeeRef().getDescriptor().getType());
long count = ecp.getExecCount(invokeSite);
if (analysisType == AnalysisType.ALL_FIT_REGIONS) {
// for this analysis we already have one miss in the original cost estimation
count--;
}
deltaCosts += count * (invokeCosts + returnCosts);
}
}
// if the code increased, the classification changed from always-hit to always-miss ..
long costs = deltaBytes > 0 ? deltaCosts : -deltaCosts;
if (analysisType == AnalysisType.ALL_FIT_REGIONS) {
// find out how many additional persistent cache misses we have
// find out border of new all-fit region
Map<MethodInfo,Integer> deltaExec = new LinkedHashMap<MethodInfo, Integer>();
int deltaCount = 0;
Set<ExecutionContext> border = new LinkedHashSet<ExecutionContext>();
if (deltaBlocks < 0) {
throw new AppInfoError("Not implemented");
} else {
for (MethodInfo miss : changes) {
for (ExecutionContext context : callGraph.getNodes(miss)) {
for (ExecutionContext invokee : callGraph.getChildren(context)) {
// not all-fit if in changeset
if (changes.contains(invokee.getMethodInfo())) continue;
// we ignore native stuff
if (invokee.getMethodInfo().isNative()) continue;
// invokee is all-fit
if (border.add(invokee)) {
deltaCount += ecp.getExecCount(invokee);
}
}
}
}
// remove old miss count
deltaCount -= getPersistentMisses(ecp, border);
}
// TODO this is not quite correct: instead of joining the reachable sets and multiplying
// with the delta count for the whole region, we should:
// - for every node in the reachable sets of the new border, sum up exec-counts of border nodes
// which contain that node in the reachable set
// - for every node in the reachable sets of the old border, subtract the exec counts of those border nodes
// - sum up invoke miss costs times calculates delta counts per node
// find out cache miss costs of new all-fit region
int regionCosts = 0;
Set<MethodInfo> visited = new LinkedHashSet<MethodInfo>();
for (ExecutionContext context : border) {
for (MethodInfo reachable : reachableMethods.get(context)) {
if (visited.add(reachable)) {
regionCosts += cache.getMissPenalty(reachable.getCode().getNumberOfWords(), cache.isLRU());
}
}
}
costs += deltaCount * regionCosts;
}
return costs;
}
///////////////////////////////////////////////////////////////////////////////////
// Notify of callgraph updates, query change sets
///////////////////////////////////////////////////////////////////////////////////
public void clearChangeSet() {
classifyChanges.clear();
}
/**
* @return all methods containing invokeSites for which the cache analysis changed
*/
public Set<MethodInfo> getClassificationChangeSet() {
return classifyChanges;
}
/**
* @param ecp exec count provider
* @return all methods for which the cache costs of the contained invoke sites changed, either due to classification
* changes or due to execution count changes.
*/
public Set<MethodInfo> getMissCountChangeSet(ExecFrequencyProvider ecp) {
if (analysisType == AnalysisType.ALWAYS_HIT) return Collections.emptySet();
Set<MethodInfo> countChanges = new LinkedHashSet<MethodInfo>(classifyChanges);
// we check the exec analysis for changed exec counts,
// need to update change sets since cache miss counts changed for cache-misses
Set<MethodInfo> methods = ecp.getChangeSet();
if (analysisType == AnalysisType.ALWAYS_MISS) {
countChanges.addAll(methods);
return countChanges;
}
for (MethodInfo method : methods) {
if (!allFit(method)) {
countChanges.add(method);
}
}
if (analysisType == AnalysisType.ALL_FIT_REGIONS) {
for (MethodInfo method : ecp.getChangeSet()) {
if (!classifyChanges.contains(method)) {
continue;
}
// all methods for which the classification changed and for which the exe count changed..
for (ExecutionContext context : callGraph.getNodes(method)) {
// add all reachable methods
countChanges.addAll(reachableMethods.get(context));
}
}
}
return countChanges;
}
public void inline(CodeModification modification, InvokeSite invokeSite, MethodInfo invokee) {
if (analysisType == AnalysisType.ALWAYS_HIT || analysisType == AnalysisType.ALWAYS_MISS) return;
Set<ExecutionContext> nodes = new LinkedHashSet<ExecutionContext>();
// We need to go down first, find all new nodes
MethodInfo invoker = invokeSite.getInvoker();
LinkedList<ExecutionContext> queue = new LinkedList<ExecutionContext>(callGraph.getNodes(invoker));
// TODO if the callgraph is compressed, we need to go down all callstring-length long paths
while (!queue.isEmpty()) {
ExecutionContext node = queue.remove();
for (ExecutionContext child : callGraph.getChildren(node)) {
if (!cacheBlocks.containsKey(child) && !nodes.contains(child)) {
nodes.add(child);
queue.add(child);
}
}
}
// update reachable set and codesize for all new nodes
updateNewNodes(nodes);
// To get the old size, use any execution node ..
ExecutionContext node = callGraph.getNodes(invoker).iterator().next();
// this includes all reachable methods, so we need to subtract them
int oldBlocks = cacheBlocks.get(node);
for (MethodInfo m : reachableMethods.get(node)) {
int size = MiscUtils.bytesToWords(getMethodSize(m));
oldBlocks -= cache.requiredNumberOfBlocks(size);
}
int size = MiscUtils.bytesToWords(getMethodSize(invoker));
int newBlocks = cache.requiredNumberOfBlocks(size);
// not using CodeModification codesize delta because it might be an estimation
int deltaBlocks = newBlocks - oldBlocks;
// now go up from the modified method, remove invokee from reachable sets if last invoke was inlined
// and update block counts
findClassificationChanges(invoker, deltaBlocks, modification.getRemovedInvokees(), true);
}
/**
* Recalculate reachable sets and block counts for the given nodes. Other nodes are *not* updated.
* @param nodes the nodes to recalculate
*/
private void updateNewNodes(Set<ExecutionContext> nodes) {
if (analysisType == AnalysisType.ALWAYS_HIT || analysisType == AnalysisType.ALWAYS_MISS || nodes.isEmpty()) {
return;
}
// create closure for new nodes and their childs
SimpleDirectedGraph<ExecutionContext,ContextEdge> closure =
GraphUtils.createSimpleGraph(callGraph.getGraph(), nodes, true);
TransitiveClosure.INSTANCE.closeSimpleDirectedGraph(closure);
updateNodes(closure, nodes, true);
}
///////////////////////////////////////////////////////////////////////////////////
// WCA Interface
///////////////////////////////////////////////////////////////////////////////////
public RecursiveStrategy<AnalysisContextLocal,WcetCost>
createRecursiveStrategy(WCETTool tool, IPETConfig ipetConfig, CacheCostCalculationMethod cacheApprox)
{
if (cacheApprox.needsInterProcIPET()) {
// TODO use method-cache for all-fit
return new GlobalAnalysis.GlobalIPETStrategy(ipetConfig);
}
return new LocalAnalysis(tool, ipetConfig) {
@Override
public WcetCost recursiveCost(RecursiveAnalysis<AnalysisContextLocal, WcetCost> stagedAnalysis,
InvokeNode n, AnalysisContextLocal ctx) {
AnalysisContextLocal newCtx = ctx;
if (analysisType == AnalysisType.ALWAYS_MISS_OR_HIT &&
allFit(stagedAnalysis.getWCETTool(), n.getInvokeSite().getInvoker(), ctx.getCallString()))
{
newCtx = ctx.withCacheApprox(CacheCostCalculationMethod.ALWAYS_HIT);
}
return super.recursiveCost(stagedAnalysis, n, newCtx);
}
};
}
///////////////////////////////////////////////////////////////////////////////////
// Private stuff
///////////////////////////////////////////////////////////////////////////////////
private int getMethodSize(MethodInfo method) {
// TODO should we use Java size to make things faster?
if (method.isNative()) return 0;
return method.getCode().getNumberOfBytes();
}
private void updateNodes(SimpleDirectedGraph<ExecutionContext,ContextEdge> closure,
Set<ExecutionContext> nodes, boolean reuseResults)
{
for (ExecutionContext node : nodes) {
if (node.getMethodInfo().isNative()) continue;
// We could make this more memory efficient, because in many cases we do not need a
// separate set for each node, but this would be more complicated to calculate
Set<MethodInfo> reachable = new LinkedHashSet<MethodInfo>();
reachable.add(node.getMethodInfo());
// we only need to add all children to the set, no need to go down the graph
for (ContextEdge edge : closure.outgoingEdgesOf(node)) {
ExecutionContext target = edge.getTarget();
if (target.getMethodInfo().isNative()) continue;
if (reuseResults && !nodes.contains(target)) {
reachable.addAll(reachableMethods.get(target));
} else {
reachable.add(target.getMethodInfo());
}
}
reachableMethods.put(node, reachable);
}
MethodCache cache = jcopter.getMethodCache();
// now we can sum up the cache blocks for all nodes in the graph
for (ExecutionContext node : nodes) {
if (node.getMethodInfo().isNative()) continue;
Set<MethodInfo> reachable = reachableMethods.get(node);
int blocks = 0;
for (MethodInfo method : reachable) {
int size = MiscUtils.bytesToWords(getMethodSize(method));
blocks += cache.requiredNumberOfBlocks(size);
}
cacheBlocks.put(node, blocks);
}
}
private Set<MethodInfo> findClassificationChanges(MethodInfo method, final int deltaBlocks,
Collection<MethodInfo> removed, final boolean update)
{
if (analysisType == AnalysisType.ALWAYS_HIT || analysisType == AnalysisType.ALWAYS_MISS ||
(deltaBlocks == 0 && removed.isEmpty()) )
{
return Collections.emptySet();
}
Set<ExecutionContext> roots = callGraph.getNodes(method);
// First, go up and find all nodes where one or more methods need to be removed from the reachable methods set
final Map<ExecutionContext,Set<MethodInfo>> removeMethods = findRemovedMethods(roots, removed);
// next, calculate blocks of removed methods
final Map<MethodInfo,Integer> blocks = new LinkedHashMap<MethodInfo, Integer>(removed.size());
for (MethodInfo m : removed) {
int size = MiscUtils.bytesToWords(getMethodSize(m));
blocks.put(m, cache.requiredNumberOfBlocks(size));
}
// finally, go up all invokers, sum up reachable method set changes and deltaBlocks per node, check all-fit
final Set<MethodInfo> changeSet = new LinkedHashSet<MethodInfo>();
DFSVisitor<ExecutionContext,ContextEdge> visitor = new EmptyDFSVisitor<ExecutionContext, ContextEdge>() {
@Override
public void preorder(ExecutionContext node) {
Set<MethodInfo> remove = removeMethods.get(node);
int oldBlocks = cacheBlocks.get(node);
int newBlocks = oldBlocks;
if (remove != null) {
if (update) {
reachableMethods.get(node).removeAll(remove);
}
for (MethodInfo r : remove) {
newBlocks -= blocks.get(r);
}
}
newBlocks += deltaBlocks;
if (update) {
cacheBlocks.put(node, newBlocks);
}
boolean oldFit = cache.allFit(oldBlocks);
boolean newFit = cache.allFit(newBlocks);
if (oldFit != newFit) {
changeSet.add(node.getMethodInfo());
if (update) {
classifyChanges.add(node.getMethodInfo());
}
}
}
};
DFSTraverser<ExecutionContext,ContextEdge> traverser =
new DFSTraverser<ExecutionContext, ContextEdge>(visitor);
traverser.traverse(callGraph.getReversedGraph(), roots);
return changeSet;
}
private Map<ExecutionContext,Set<MethodInfo>> findRemovedMethods(Set<ExecutionContext> roots,
Collection<MethodInfo> removed)
{
Map<ExecutionContext,Set<MethodInfo>> removeMethods = new LinkedHashMap<ExecutionContext, Set<MethodInfo>>();
LinkedHashSet<ExecutionContext> queue = new LinkedHashSet<ExecutionContext>(roots);
while (!queue.isEmpty()) {
ExecutionContext node = queue.iterator().next();
queue.remove(node);
boolean changed = false;
boolean isRoot = roots.contains(node);
boolean isNew = false;
// we initialize (lazily) by assuming that all removed methods are no longer reachable in any node,
// and then removing entries from the set if they are found to be still reachable.
// This ensures that the size of the sets only decreases and we eventually reach a fixpoint
Set<MethodInfo> set = removeMethods.get(node);
if (set == null) {
set = new LinkedHashSet<MethodInfo>(removed.size());
removeMethods.put(node, set);
for (MethodInfo m : removed) {
// initially add method to remove set if it is reachable from this node
if (reachableMethods.get(node).contains(m)) {
set.add(m);
}
}
changed = true;
isNew = true;
}
// check if any of the methods to remove have been removed from *all* childs and can therefore
// be removed from this node
for (MethodInfo r : removed) {
// already removed
if (!set.contains(r)) continue;
for (ExecutionContext child : callGraph.getChildren(node)) {
// we ignore native methods in the cache analysis
if (child.getMethodInfo().isNative()) continue;
// skip childs which will be removed
if (isRoot && removed.contains(child.getMethodInfo())) continue;
// TODO this is incorrect for cyclic call graphs.. need to fix this!
if (reachableMethods.get(child).contains(r)) {
set.remove(r);
changed = true;
}
}
}
if (isNew && set.isEmpty()) {
// we did not remove anything here and we did not visit the parents yet, so nothing changes
changed = false;
}
if (changed) {
// we have found more methods, need to update parents
queue.addAll(callGraph.getParents(node));
}
}
return removeMethods;
}
private Set<ExecutionContext> findAllFitBorder(Collection<ExecutionContext> nodes) {
final Set<ExecutionContext> border = new LinkedHashSet<ExecutionContext>();
DFSVisitor<ExecutionContext,ContextEdge> visitor = new EmptyDFSVisitor<ExecutionContext, ContextEdge>() {
@Override
public boolean visitNode(ExecutionContext parent, ContextEdge edge, ExecutionContext node, DFSEdgeType type, Collection<ContextEdge> outEdges, int depth) {
if (type == DFSEdgeType.ROOT || type == DFSEdgeType.BACK_EDGE) {
// skip the root, ignore back edges
return true;
}
if (!allFit(node)) {
// we do not go up from always-miss nodes, so the 'parent' must be all-fit
border.add(parent);
return false;
}
return true;
}
};
DFSTraverser<ExecutionContext,ContextEdge> traverser = new DFSTraverser<ExecutionContext, ContextEdge>(visitor);
traverser.traverse(callGraph.getReversedGraph(), nodes);
return border;
}
private long getPersistentMisses(ExecFrequencyProvider ecp, Collection<ExecutionContext> nodes) {
long count = 0;
for (ExecutionContext border : findAllFitBorder(nodes)) {
count += ecp.getExecCount(border);
}
return count;
}
}