/*
* This file is part of JOP, the Java Optimized Processor
* see <http://www.jopdesign.com/>
*
* Copyright (C) 2010, Benedikt Huber (benedikt.huber@gmail.com)
*
* 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.wcet.analysis.cache;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import lpsolve.LpSolveException;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.Type;
import org.apache.log4j.Logger;
import com.jopdesign.common.MethodCode;
import com.jopdesign.common.MethodInfo;
import com.jopdesign.common.code.CallString;
import com.jopdesign.common.code.ControlFlowGraph;
import com.jopdesign.common.code.ControlFlowGraph.CFGNode;
import com.jopdesign.common.code.InvokeSite;
import com.jopdesign.common.code.Segment;
import com.jopdesign.common.code.SuperGraph.ContextCFG;
import com.jopdesign.common.code.SuperGraph.SuperEdge;
import com.jopdesign.common.code.SuperGraph.SuperGraphEdge;
import com.jopdesign.common.code.SuperGraph.SuperGraphNode;
import com.jopdesign.common.code.SuperGraph.SuperInvokeEdge;
import com.jopdesign.common.code.SuperGraph.SuperReturnEdge;
import com.jopdesign.common.graphutils.Pair;
import com.jopdesign.common.misc.AppInfoException;
import com.jopdesign.common.misc.Filter;
import com.jopdesign.common.misc.MiscUtils;
import com.jopdesign.common.misc.MiscUtils.F1;
import com.jopdesign.wcet.WCETTool;
import com.jopdesign.wcet.analysis.InvalidFlowFactException;
import com.jopdesign.wcet.ipet.IPETConfig.CacheCostCalculationMethod;
import com.jopdesign.wcet.ipet.IPETSolver;
import com.jopdesign.wcet.jop.MethodCache;
/**
* <p>Cache persistence analysis for the variable block Method cache.</p>
* @throws InvalidFlowFactException
* @author Benedikt Huber <benedikt.huber@gmail.com>
*/
public class MethodCacheAnalysis extends CachePersistenceAnalysis {
static final String KEY = "wcet.MethodCacheAnalysis";
protected final WCETTool wcetTool;
private final F1<SuperGraphEdge, Long> NUMBER_OF_BLOCKS =
new F1<SuperGraphEdge,Long>() {
@Override
public Long apply(SuperGraphEdge e) {
MethodInfo mi = e.getTarget().getCfg().getMethodInfo();
return (long) methodCache.requiredNumberOfBlocks(mi);
}
};
private final F1<SuperGraphEdge, Long> EDGE_MISS_COST = new F1<SuperGraphEdge,Long>() {
@Override
public Long apply(SuperGraphEdge accessEdge) {
return getMissCost(accessEdge);
}
};
private MethodCache methodCache;
public MethodCacheAnalysis(WCETTool wcetTool) {
this.wcetTool = wcetTool;
this.methodCache = wcetTool.getWCETProcessorModel().getMethodCache();
}
public MethodCache getMethodCache() {
return methodCache;
}
@Override
public Set<SuperGraphEdge> addCacheCost(Segment segment, IPETSolver<SuperGraphEdge> ipetSolver,
CacheCostCalculationMethod cacheCostCalc) throws InvalidFlowFactException, LpSolveException {
if(this.getMethodCache().getNumBlocks() == 0) return new HashSet<SuperGraphEdge>();
return super.addCacheCost(segment, ipetSolver, cacheCostCalc);
}
/**
* Add always miss cost: for each access to the method cache, add cost of access
* @param segment
* @param ipetSolver
*/
@Override
public Set<SuperGraphEdge> addMissAlwaysCost(
Segment segment,
IPETSolver<SuperGraphEdge> ipetSolver) {
Iterable<SuperGraphEdge> accessEdges = collectCacheAccesses(segment);
for(SuperGraphEdge accessEdge: accessEdges) {
if(! segment.includesEdge(accessEdge)) {
throw new AssertionError("Subsegment edge not in segment!: "+accessEdge);
}
}
return addFixedCostEdges(accessEdges, ipetSolver, EDGE_MISS_COST, KEY+"_am",0);
}
/**
* Add miss once cost: for each method cache persistence segment, add maximum miss cost to the segment entries
* @param segment
* @param ipetSolver
* @param peristenceChecks which checks to perform
* @throws LpSolveException
* @throws InvalidFlowFactException
*/
@Override
public Set<SuperGraphEdge> addMissOnceCost(Segment segment,
IPETSolver<SuperGraphEdge> ipetSolver, EnumSet<PersistenceCheck> checks)
throws InvalidFlowFactException, LpSolveException {
Set<SuperGraphEdge> missEdges = new HashSet<SuperGraphEdge>();
Map<SuperGraphEdge, Long> extraCost = new HashMap<SuperGraphEdge, Long>();
Collection<Segment> cover = findPersistenceSegmentCover(segment, checks, true, extraCost);
int tag = 0;
for(Segment persistenceSegment : cover) {
tag++;
/* Collect all cache accesses */
long cost = computeMissOnceCost(persistenceSegment, getCacheAccessesByTag(persistenceSegment).entrySet(),
EDGE_MISS_COST, true, KEY+".addMissOnceCost", wcetTool);
F1<SuperGraphEdge, Long> costModel = MiscUtils.const1(cost);
Set<SuperGraphEdge> costEdges = addFixedCostEdges(persistenceSegment.getEntryEdges(), ipetSolver,
costModel, KEY + "_miss_once", tag);
missEdges.addAll(costEdges);
}
for(Entry<SuperGraphEdge, Long> entry : extraCost.entrySet()) {
missEdges.add(fixedAdditionalCostEdge(entry.getKey(), KEY+"_am", 0, entry.getValue(), ipetSolver));
}
return missEdges;
}
/**
* Add miss once constraints for all subsegments in the persistence cover of the given segment
* @param segment
* @param ipetSolver
* @return
*/
@Override
public Set<SuperGraphEdge> addMissOnceConstraints(Segment segment,
IPETSolver<SuperGraphEdge> ipetSolver) {
Set<SuperGraphEdge> missEdges = new HashSet<SuperGraphEdge>();
Map<SuperGraphEdge, Long> extraCost = new HashMap<SuperGraphEdge, Long>();
Collection<Segment> cover = findPersistenceSegmentCover(segment, EnumSet.allOf(PersistenceCheck.class), false, extraCost);
int segmentCounter = 0;
for(Segment persistenceSegment : cover) {
/* we need to distinguish edges which are shared between persistence segments */
String key = KEY +"_" + (++segmentCounter);
missEdges.addAll(addPersistenceSegmentConstraints(
persistenceSegment,
getCacheAccessesByTag(persistenceSegment).entrySet(),
ipetSolver,
EDGE_MISS_COST,
key));
}
for(Entry<SuperGraphEdge, Long> entry : extraCost.entrySet()) {
missEdges.add(fixedAdditionalCostEdge(entry.getKey(), KEY+"_am", 0, entry.getValue(), ipetSolver));
}
return missEdges;
}
@Override
public Set<SuperGraphEdge> addGlobalAllFitConstraints(Segment segment,
IPETSolver<SuperGraphEdge> ipetSolver) {
return addPersistenceSegmentConstraints(segment,
getCacheAccessesByTag(segment).entrySet(),
ipetSolver,
EDGE_MISS_COST,
KEY);
}
/** Find a segment cover (i.e., a set of segments covering all execution paths)
* where each segment in the set is persistent (a cache persistence region (CPR))
*
* <h2>The simplest algorithm for a segment S (for acyclic callgraphs)</h2>
* <ul><li/>Check whether S itself is CPR; if so, return S
* <li/>Otherwise, create subsegments S' for each invoked method,
* <li/>and single node segments for each access
* </ul>
* @param segment the parent segment
* @param checks the strategy to use to determine whether a segment is a persistence region
* @param avoidOverlap whether overlapping segments should be avoided
* @param extraCostOut additional cost for edges which are considered as always-miss or not-cached
* @return
*/
protected Collection<Segment> findPersistenceSegmentCover(Segment segment, EnumSet<PersistenceCheck> checks,
boolean avoidOverlap, Map<SuperGraphEdge, Long> extraCostOut) {
List<Segment> cover = new ArrayList<Segment>();
/* We currently only support entries to one CFG */
Set<ContextCFG> entryMethods = new HashSet<ContextCFG>();
for(SuperGraphEdge entryEdge : segment.getEntryEdges()) {
entryMethods.add(entryEdge.getTarget().getContextCFG());
}
if(entryMethods.size() != 1) {
throw new AssertionError("findPersistenceSegmentCover: only supporting segments with unique entry method");
}
if(this.isPersistenceRegion(segment, checks)) {
// System.err.println("Adding cover segment for: "+entryMethods);
cover.add(segment);
} else {
for(Pair<SuperInvokeEdge, SuperReturnEdge> invocation : segment.getCallSitesFrom(entryMethods.iterator().next())) {
ContextCFG callee = invocation.first().getCallee();
// System.err.println("Recursively analyzing: "+callee);
Segment subSegment = Segment.methodSegment(callee, segment.getSuperGraph());
Collection<Segment> subRegions = findPersistenceSegmentCover(subSegment, checks, avoidOverlap, extraCostOut);
cover.addAll(subRegions);
SuperReturnEdge rEdge = invocation.second();
MiscUtils.incrementBy(extraCostOut, rEdge, getMissCost(rEdge), 0);
}
}
return cover;
}
public boolean isPersistenceRegion(WCETTool wcetTool, MethodInfo m, CallString cs, EnumSet<PersistenceCheck> tests) {
/* fast path for the optimizer (segments are still slow) */
if(tests == EnumSet.of(PersistenceCheck.CountTotal)) {
List<MethodInfo> methods = wcetTool.getCallGraph().getReachableImplementations(m, cs);
long blocks = 0;
for (MethodInfo reachable : methods) {
blocks += methodCache.requiredNumberOfBlocks(reachable);
}
return methodCache.allFit(blocks);
}
Segment segment = Segment.methodSegment(m, cs, wcetTool, wcetTool.getCallstringLength(), wcetTool);
return isPersistenceRegion(segment, tests);
}
/**
* Perform a heuristic check whether in the given segment all methods are persistent
* @param segment
* @param tests which checks to perform
* @return true if all methods are persistent in the method cache, false if not sure
* @throws LpSolveException
* @throws InvalidFlowFactException
*/
public boolean isPersistenceRegion(Segment segment, EnumSet<PersistenceCheck> tests) {
int cacheWays = getNumberOfWays();
long usedWays = cacheWays + 1;
if(tests.contains(PersistenceCheck.CountTotal)) {
usedWays = countDistinctBlocksUsed(segment);
if(usedWays <= cacheWays) return true;
}
if(tests.contains(PersistenceCheck.CountRelaxed)) {
try {
usedWays = countDistinctBlocksAccessed(segment, false);
if(usedWays <= cacheWays) return true;
if(usedWays >= cacheWays*2) return false;
} catch(Exception ex) {
WCETTool.logger.error("Count Distinct Cache Blocks (Relaxed LP) failed: "+ex);
}
}
if(tests.contains(PersistenceCheck.CountILP)) {
try {
usedWays = countDistinctBlocksAccessed(segment, true);
if(usedWays <= cacheWays) return true;
} catch(Exception ex) {
WCETTool.logger.error("Count Distinct Cache Blocks (Relaxed LP) failed: "+ex);
}
}
return false;
}
protected int getNumberOfWays() {
/* used by the isPersistenceRegion strategy */
return methodCache.getNumBlocks();
}
/**
* Calculate the total number of distinct method cache blocks possibly accessed in this
* segment (not restricted to one execution)
* @param segment The segment to consider
* @return
*/
public long countDistinctBlocksUsed(Segment segment) {
long blocks = 0;
for (MethodInfo reachable : this.getCacheAccessesByTag(segment).keySet()) {
blocks += methodCache.requiredNumberOfBlocks(reachable);
}
return blocks;
}
/**
* Analyze the maximum number of distinct cache lines (ways) possibly accessed during
* one execution of the segment
* @param segment the segment to analyze
* @param use integer variables (more expensive, more accurate)
* @return
* @throws InvalidFlowFactException
* @throws LpSolveException
*/
public long countDistinctBlocksAccessed(Segment segment, boolean useILP) throws InvalidFlowFactException, LpSolveException {
return computeMissOnceCost(segment, getCacheAccessesByTag(segment).entrySet(),
NUMBER_OF_BLOCKS, useILP, KEY+".countDistinctCacheBlocks", wcetTool);
}
protected Map<MethodInfo, List<SuperGraphEdge>> getCacheAccessesByTag(Segment segment) {
/* Collect all method cache accesses */
Iterable<SuperGraphEdge> cacheAccessEdges = collectCacheAccesses(segment);
return groupAccessEdges(cacheAccessEdges);
}
/**
* @param cacheAccessEdges
* @return
*/
private Map<MethodInfo, List<SuperGraphEdge>> groupAccessEdges(
Iterable<SuperGraphEdge> cacheAccessEdges) {
/* Group method cache access by method */
F1<SuperGraphEdge, MethodInfo> getMethodInfo = new F1<SuperGraphEdge, MethodInfo>() {
public MethodInfo apply(SuperGraphEdge v) { return v.getTarget().getContextCFG().getCfg().getMethodInfo(); }
};
return MiscUtils.group(getMethodInfo, null, cacheAccessEdges);
}
/**
* Collect all method cache accesses in the segment: These are all supergraph edges,
* plus all entry edges from the CFG entry.
* @param segment
* @return
*/
private Iterable<SuperGraphEdge> collectCacheAccesses(final Segment segment) {
return new Filter<SuperGraphEdge>() {
@Override
protected boolean include(SuperGraphEdge e) {
if(e instanceof SuperEdge) {
return true;
} else if(segment.getEntryEdges().contains(e)) {
return true;
} else {
return false;
}
}
}.filter(segment.getEdges());
}
/**
* Get miss cost for an edge accessing the method cache
* @param accessEdge either a SuperInvoke or SuperReturn edge, or an entry edge of the segment analyzed
* @return maximum miss penalty (in cycles)
*/
private long getMissCost(SuperGraphEdge accessEdge) {
SuperGraphNode accessed = accessEdge.getTarget();
ControlFlowGraph cfg = accessed.getCfg();
if(accessEdge instanceof SuperReturnEdge) {
/* return edge: return cost */
Type returnType = accessEdge.getSource().getCfg().getMethodInfo().getType();
return methodCache.getMissPenaltyOnReturn(cfg.getNumberOfWords(), returnType);
} else if(accessEdge instanceof SuperInvokeEdge) {
InstructionHandle invokeIns = ((SuperInvokeEdge) accessEdge).getInvokeNode().getInvokeSite().getInstructionHandle();
return methodCache.getMissPenaltyOnInvoke(cfg.getNumberOfWords(), invokeIns.getInstruction());
} else {
/* entry edge of the segment: can be invoke or return cost */
return methodCache.getMissPenalty(cfg.getNumberOfWords(), false);
}
}
/* utility functions */
/**
* Get the maximum method cache miss penalty for invoking {@code invoked} and returning to {@code invoker}.<br/>
* Also works with virtual invokes (taking the maximum cost)
* @param invokeSite the invoke site
* @param context call context
* @return miss penalty in cycles
*/
public long getInvokeReturnMissCost(InvokeSite invokeSite, CallString context) {
ControlFlowGraph invokerCfg = wcetTool.getFlowGraph(invokeSite.getInvoker());
long rMiss = methodCache.getMissPenaltyOnReturn(invokerCfg.getNumberOfWords(), invokeSite.getInvokeeRef().getDescriptor().getType());
long iMissMax = 0;
for(MethodInfo target : wcetTool.findImplementations(invokeSite.getInvoker(), invokeSite.getInstructionHandle(), context)) {
ControlFlowGraph invokedCfg = wcetTool.getFlowGraph(target);
long iMiss = methodCache.getMissPenaltyOnInvoke(invokedCfg.getNumberOfWords(), invokeSite.getInstructionHandle().getInstruction());
if(iMiss > iMissMax) iMissMax = iMiss;
}
return iMissMax + rMiss;
}
/**
* Check that cache is big enough to hold any method possibly invoked
* Return largest method
*/
public static MethodInfo checkCache(WCETTool wcetTool, Iterable<MethodInfo> methods) throws AppInfoException {
MethodCache methodCache = wcetTool.getWCETProcessorModel().getMethodCache();
int maxWords = 0;
MethodInfo largestMethod = null;
// It is inconvenient for testing to take all methods into account
// for (ClassInfo ci : project.getAppInfo().getClassInfos()) {
for (MethodInfo mi : methods) {
MethodCode code = mi.getCode();
if (code == null) continue;
// FIXME: using getNumberOfBytes(false) here to be compatible to old behaviour.
// should probably be getNumberOfBytes()
int size = code.getNumberOfBytes(false);
int words = MiscUtils.bytesToWords(size);
if (!methodCache.fitsInCache(words)) {
throw new AppInfoException("Cache to small for target method: " + mi.getFQMethodName() + " / " + words + " words");
}
if (words >= maxWords) {
largestMethod = mi;
maxWords = words;
}
}
return largestMethod;
}
/**
* Compute the maximal total cache-miss penalty for <strong>invoking and executing</strong>
* m.
* <p>
* Precondition: The set of all methods reachable from <code>m</code> fit into the cache
* </p><p>
* Algorithm: If all methods reachable from <code>m</code> (including <code>m</code>) fit
* into the cache, we can compute the WCET of <m> using the {@code ALWAYS_HIT} cache
* approximation, and then add the sum of cache miss penalties for every reachable method.
* </p><p>
* Note that when using this approximation, we attribute the
* total cache miss cost to the invocation of that method.
* </p><p>
* Explanation: We know that there is only one cache miss per method, but for FIFO caches we
* do not know when the cache miss will occur (on return or invoke), except for leaf methods.
* Let <code>h</code> be the number of cycles hidden by <strong>any</strong> return or
* invoke instructions. Then the cache miss penalty is bounded by <code>(b-h)</code> per
* method.
* </p>
* @param m The method invoked
* @return the cache miss penalty
* @deprecated ported from the old method cache analysis framework
*/
@Deprecated
public long getMissOnceCummulativeCacheCost(MethodInfo m, boolean assumeOnInvoke) {
long miss = 0;
for (MethodInfo reachable : wcetTool.getCallGraph().getReachableImplementationsSet(m)) {
miss += getMissOnceCost(reachable, assumeOnInvoke);
}
Logger.getLogger(this.getClass()).debug("getMissOnceCummulativeCacheCost for " + m + "/" + (assumeOnInvoke ? "invoke" : "return") + ":" + miss);
return miss;
}
/**
* @param mi the method info invoked
* @param assumeOnInvoke whether we may assume the miss happens on invoke
* @return miss penalty in cycles
*/
public long getMissOnceCost(MethodInfo mi, boolean assumeOnInvoke) {
int words = wcetTool.getFlowGraph(mi).getNumberOfWords();
long missCycles = methodCache.getMissPenalty(words, true);
if(! assumeOnInvoke) {
long rMissCycles = methodCache.getMissPenalty(words, false);
missCycles = Math.max( missCycles, rMissCycles );
}
return missCycles;
}
}