/*
* This file is part of JOP, the Java Optimized Processor
* see <http://www.jopdesign.com/>
*
* Copyright (C) 2010-2011, 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 static com.jopdesign.common.misc.MiscUtils.addToSet;
import java.io.PrintStream;
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.ARRAYLENGTH;
import org.apache.bcel.generic.ArrayInstruction;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.FieldInstruction;
import org.apache.bcel.generic.GETFIELD;
import org.apache.bcel.generic.INVOKEINTERFACE;
import org.apache.bcel.generic.INVOKEVIRTUAL;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.PUTFIELD;
import org.apache.bcel.generic.ReferenceType;
import org.apache.bcel.generic.Type;
import org.apache.log4j.Logger;
import com.jopdesign.common.MethodInfo;
import com.jopdesign.common.code.BasicBlock;
import com.jopdesign.common.code.CallString;
import com.jopdesign.common.code.ControlFlowGraph;
import com.jopdesign.common.code.ControlFlowGraph.CFGNode;
import com.jopdesign.common.code.ExecutionContext;
import com.jopdesign.common.code.Segment;
import com.jopdesign.common.code.SuperGraph;
import com.jopdesign.common.code.SuperGraph.ContextCFG;
import com.jopdesign.common.code.SuperGraph.IntraEdge;
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.Iterators;
import com.jopdesign.common.misc.MiscUtils;
import com.jopdesign.common.misc.MiscUtils.F1;
import com.jopdesign.dfa.DFATool;
import com.jopdesign.dfa.analyses.SymbolicAddress;
import com.jopdesign.dfa.analyses.SymbolicPointsTo;
import com.jopdesign.dfa.framework.BoundedSetFactory.BoundedSet;
import com.jopdesign.dfa.framework.ContextMap;
import com.jopdesign.wcet.WCETTool;
import com.jopdesign.wcet.analysis.GlobalAnalysis;
import com.jopdesign.wcet.analysis.InvalidFlowFactException;
import com.jopdesign.wcet.analysis.cache.ObjectCacheAnalysis.AccessCostInfo;
import com.jopdesign.wcet.annotations.BadAnnotationException;
import com.jopdesign.wcet.ipet.IPETConfig;
import com.jopdesign.wcet.ipet.IPETSolver;
import com.jopdesign.wcet.jop.ObjectCache;
import com.jopdesign.wcet.jop.ObjectCache.ObjectCacheCost;
/** Analysis of the used object references.
* Goal: Detect persistence scopes.
*
* This is the current consensus:
* <ul><li/>One cache line per object
* <li/> Only consider getfield. putfield does not modify cache
* <li/> Handle access should be configurable (HANDLE_ACCESS = false or true)
* </ul>
* For the general technique, see {@link MethodCacheAnalysis#countDistinctCacheBlocks()}
* <h2>Bytecodes using object references</h2>
* <ul>
* <li/> getfield (top of stack)
* <li/> putfield (second on stack)
* <li/> arraylen (top of stack)
* <li/> a*load (second on stack)
* <li/> a*store (third on stack)
* </ul>
* <h2>Reference Analysis</h2>
* For each scope, we do the following:
* <ol>
* <li/> Perform a local, symbolic points-to analysis
* <li/> Traverse all instructions, and collect for each reference the basic blocks it might
* be used in. Additionally, the cost of each basic block is set to the number of
* TOP references accessed.
* <li/> For each reference, add a decision variable denoting whether it is used at all,
* and add corresponding constraints.
* </ol>
*
* <ul>
* <li/>FIXME: [cache-analysis] Handle subtyping when dealing with aliases, or use a store-based approach
* </ul>
* @author Benedikt Huber <benedikt.huber@gmail.com>
*
*/
public class ObjectCacheAnalysis extends CachePersistenceAnalysis {
private static final String KEY = "wcet.ObjectCacheAnalysis";
/* Only consider getfield (false) or all handle accesses */
private static boolean ALL_HANDLE_ACCESSES = false;
/**
* Purpose: This class encapsulates the results from the object reference DFA
*/
public static class LocalPointsToResult {
private HashMap<InstructionHandle, ContextMap<CallString, BoundedSet<SymbolicAddress>>> pointsTo;
private LocalPointsToResult(HashMap<InstructionHandle, ContextMap<CallString, BoundedSet<SymbolicAddress>>> pTo) {
pointsTo = pTo;
}
public BoundedSet<SymbolicAddress> get(InstructionHandle ih, CallString cs) {
ContextMap<CallString, BoundedSet<SymbolicAddress>> csmap = pointsTo.get(ih);
if(csmap != null) {
return csmap.get(cs);
} else {
return null;
}
}
public boolean containsKey(InstructionHandle ih) {
return pointsTo.containsKey(ih);
}
public Set<SymbolicAddress> getAddressSet() {
Set<SymbolicAddress> addressSet = new HashSet<SymbolicAddress>();
for(ContextMap<CallString, BoundedSet<SymbolicAddress>> entry : pointsTo.values()) {
for(BoundedSet<SymbolicAddress> aset : entry.values()) {
if(! aset.isSaturated()) addressSet.addAll(aset.getSet());
}
}
return addressSet;
}
}
/* class for checking whether a basic block is executed as most once in a scope */
private class ExecOnceQuery implements MiscUtils.Query<InstructionHandle> {
private ExecuteOnceAnalysis eoAna;
private ExecutionContext scope;
public ExecOnceQuery(ExecuteOnceAnalysis eoAnalysis, ExecutionContext scope) {
this.eoAna = eoAnalysis;
this.scope = scope;
}
public boolean query(InstructionHandle a) {
ControlFlowGraph cfg = project.getFlowGraph(scope.getMethodInfo());
CFGNode n = cfg.getHandleNode(a);
if(n == null) {
Logger.getLogger("Object Cache Analysis").info("No node for instruction "+a);
return false;
} else {
return eoAna.isExecutedOnce(scope, n);
}
}
}
/* Simple Cost Models for our Object Cache */
public static class ObjectCacheCostModel {
public static final ObjectCacheCostModel COUNT_REF_TAGS = new ObjectCacheCostModel(0,1,0);;
public static final ObjectCacheCostModel COUNT_FIELD_TAGS = new ObjectCacheCostModel(1,0,0);
private long loadCacheBlockCost;
private long replaceLineCost;
private long fieldAccessCostBypass;
public ObjectCacheCostModel(long loadCacheBlockCost, long replaceLineCost, long fieldAccessCostBypass)
{
this.loadCacheBlockCost = loadCacheBlockCost;
this.replaceLineCost = replaceLineCost;
this.fieldAccessCostBypass = fieldAccessCostBypass;
}
/**
* @return the loadFieldCost
*/
public long getCacheBlockCost() {
return loadCacheBlockCost;
}
/**
* @return the loadCacheLineCost
*/
public long getReplaceLineCost() {
return replaceLineCost;
}
/**
* @return the fieldAccessCostBypass
*/
public long getFieldAccessCostBypass() {
return fieldAccessCostBypass;
}
/**
* @return
*/
public long getLoadCacheBlockCost() {
return this.loadCacheBlockCost;
}
}
public static class AccessCostInfo {
HashMap<SymbolicAddress, Map<SuperGraphNode, Integer>> refAccessSets;
HashMap<SymbolicAddress, Map<SuperGraphNode, Integer>> blockAccessSets;
Map<SuperGraphNode,Long> staticCostMap;
Map<SuperGraphNode,Long> bypassCostMap;
public AccessCostInfo() {
refAccessSets = new HashMap<SymbolicAddress,Map<SuperGraphNode, Integer>>();
blockAccessSets = new HashMap<SymbolicAddress,Map<SuperGraphNode, Integer>>();
staticCostMap = new HashMap<SuperGraphNode, Long>();
bypassCostMap = new HashMap<SuperGraphNode, Long>();
}
/**
* @param node
* @return the bypass cost for executing the given node once
*/
public long getBypassCost(SuperGraphNode node) {
return bypassCostMap.get(node);
}
/**
* @param node
* @return
*/
public long getMissCost(SuperGraphNode node) {
return staticCostMap.get(node) - getBypassCost(node);
}
/**
* @param node
* @return
* @return always miss and bypass cost for the node
*/
public long getStaticCost(SuperGraphNode node) {
if(! staticCostMap.containsKey(node)) return 0;
return staticCostMap.get(node);
}
public Map<SuperGraphNode, Long> getStaticCostMap() {
return staticCostMap;
}
/**
* @return set of all referenced object names
*/
public Set<SymbolicAddress> getReferencedObjectNames() {
return refAccessSets.keySet();
}
/**
* @return set of all reference blocks for object names
*/
public Set<SymbolicAddress> getReferencedBlocks() {
return blockAccessSets.keySet();
}
public void addRefAccess(SymbolicAddress ref, SuperGraphNode node) {
addAccessSite(refAccessSets, ref, node);
}
public void addBlockAccess(SymbolicAddress ref, SuperGraphNode node) {
addAccessSite(blockAccessSets, ref, node);
}
public void putBypassCost(SuperGraphNode node, long bypassCost) {
bypassCostMap.put(node,bypassCost);
}
public void putStaticCost(SuperGraphNode node, long staticCost) {
staticCostMap.put(node, staticCost);
}
private void addAccessSite(
Map<SymbolicAddress, Map<SuperGraphNode, Integer>> accessSets,
SymbolicAddress ref,
SuperGraphNode n) {
Map<SuperGraphNode, Integer> accessSet = accessSets.get(ref);
if(accessSet == null) {
accessSet = new HashMap<SuperGraphNode, Integer>();
accessSets.put(ref, accessSet);
}
Integer oldCount = accessSet.get(n);
if(oldCount == null) {
accessSet.put(n,1);
} else {
accessSet.put(n,oldCount+1);
}
}
/**
* @param segment
* @return
*/
public Set<Entry<SymbolicAddress, Set<SuperGraphEdge>>> getRefAccesses(Segment segment) {
return accessEdges(segment, refAccessSets).entrySet();
}
/**
* @param segment
* @return
*/
public Set<Entry<SymbolicAddress, Set<SuperGraphEdge>>> getBlockAccesses(
Segment segment) {
return accessEdges(segment, blockAccessSets).entrySet();
}
/**
* Tedious datatype transformation. Tried with iterators, but the result looks reallly horrible.
* @param acccessSet
* @return
*/
private Map<SymbolicAddress, Set<SuperGraphEdge>> accessEdges(
Segment segment, HashMap<SymbolicAddress, Map<SuperGraphNode, Integer>> accessSet) {
Map<SymbolicAddress, Set<SuperGraphEdge>> accessesByAddress =
new HashMap<SymbolicAddress, Set<SuperGraphEdge>>();
for(Entry<SymbolicAddress, Map<SuperGraphNode, Integer>> accesses : accessSet.entrySet()) {
Set<SuperGraphEdge> accessEdges = new HashSet<SuperGraphEdge>();
accessesByAddress.put(accesses.getKey(), accessEdges);
for( Entry<SuperGraphNode, Integer> access : accesses.getValue().entrySet()) {
Iterators.addAll(accessEdges, segment.incomingEdgesOf(access.getKey()));
}
}
return accessesByAddress;
}
/**
* Dump the information to the given stream
* @param out a print stream for output
*/
public void dump(PrintStream out, int indentAmount) {
String indent = (indentAmount>0?String.format("%"+indentAmount+"s",""):"");
out.println(indent+"Accessed References: ");
dumpAccessMap(out, indentAmount+2, refAccessSets);
out.println(indent+"Accessed Blocks: ");
dumpAccessMap(out, indentAmount+2, blockAccessSets);
out.println(indent+"Static Cost Map: ");
dumpCostMap(out, indentAmount+4,staticCostMap);
out.println(indent+"Bypass Cost Map: ");
dumpCostMap(out, indentAmount+4,bypassCostMap);
}
private void dumpCostMap(PrintStream out, int indentAmount, Map<SuperGraphNode, Long> costMap) {
for(Entry<SuperGraphNode, Long> costEntry : costMap.entrySet()) {
if(costEntry.getValue() == 0) continue;
out.printf("%"+indentAmount+"s%-40s: %d", "", costEntry.getKey(), costEntry.getValue());
}
}
private void dumpAccessMap(PrintStream out, int indentAmount,
HashMap<SymbolicAddress, Map<SuperGraphNode, Integer>> accessMap) {
for(Entry<SymbolicAddress, Map<SuperGraphNode, Integer>> entry : accessMap.entrySet()) {
out.printf("%"+indentAmount+"s%-30s:", "", entry.getKey());
for(Entry<SuperGraphNode, Integer> node : entry.getValue().entrySet()) {
out.printf(" %s [%d]",node.getKey(), node.getValue());
}
out.println();
}
}
}
private static class ObjectCacheIPETModel {
public Set<SuperGraphEdge> staticCostEdges;
public AccessCostInfo accessCostInfo;
public Set<SuperGraphEdge> refMissEdges;
public Set<SuperGraphEdge> blockMissEdges;
}
/* Whether to use a 'single field' cache, i.e., use fields as tags */
private boolean fieldAsTag;
/* The maximum index for cached fields */
private int maxCachedFieldIndex;
/* ld(blockSize) */
private int blockIndexBits;
/* Maximum number of objects tracked for one reference */
private int maxSetSize;
/* Those type which have an unbounded number of objects in the given scope
* FIXME: maybe we should switch to allocation sites (more precise, no subtyping) */
private Map<ExecutionContext, Set<String>> saturatedTypes;
/* Maximum number of objects which are loaded into the object cache (at least one field has to be accessed) */
private Map<ExecutionContext, Long> maxCachedTagsAccessed;
/* The set of symbolic object names loaded into the cache (without saturated references) */
private Map<ExecutionContext, Set<SymbolicAddress>> tagSet;
private WCETTool project;
private ObjectCache objectCache;
public ObjectCacheAnalysis(WCETTool p, ObjectCache oc) {
this.project = p;
this.objectCache = oc;
this.fieldAsTag = oc.isFieldCache();
this.maxSetSize = oc.getAssociativity();
this.maxCachedFieldIndex = oc.getMaxCachedFieldIndex();
this.blockIndexBits = 0;
for(int i = 1; i < oc.getBlockSize(); i<<=1) {
blockIndexBits++;
}
saturatedTypes = new HashMap<ExecutionContext, Set<String>>();
maxCachedTagsAccessed = new HashMap<ExecutionContext, Long>();
tagSet = new HashMap<ExecutionContext, Set<SymbolicAddress>>();
}
/**
* 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) {
/* get always miss cost */
AccessCostInfo missCostInfo = extractAccessesAndCosts(segment, null, objectCache.getCostModel());
return addStaticCost(segment, missCostInfo, ipetSolver);
}
private Set<SuperGraphEdge> addStaticCost(
Segment segment,
AccessCostInfo accessInfo,
IPETSolver<SuperGraphEdge> ipetSolver) {
HashSet<SuperGraphEdge> costEdges = new HashSet<SuperGraphEdge>();
for(Entry<SuperGraphEdge, Long> entry: nodeToEdgeCost(segment, accessInfo.getStaticCostMap()).entrySet()) {
costEdges.add(fixedAdditionalCostEdge(entry.getKey(), KEY+"_"+"am", 0, entry.getValue(), ipetSolver));
}
return costEdges;
}
/**
* Add miss once cost: for each method cache persistence segment, add maximum miss cost to the segment entries
* @param segment
* @param ipetSolver
* @param checks
* @throws LpSolveException
* @throws InvalidFlowFactException
*/
@Override
public Set<SuperGraphEdge> addMissOnceCost(Segment segment,
IPETSolver<SuperGraphEdge> ipetSolver, EnumSet<PersistenceCheck> checks)
throws InvalidFlowFactException, LpSolveException {
Set<SuperGraphEdge> missCostEdges = new HashSet<SuperGraphEdge>();
Set<SuperGraphNode> alwaysMissNodes = new HashSet<SuperGraphNode>();
Collection<Segment> cover =
findPersistenceSegmentCover(segment, EnumSet.allOf(PersistenceCheck.class), false, alwaysMissNodes);
int tag = 0;
for(Segment persistenceSegment : cover) {
tag++;
/* Compute cost for persistence segment */
HashSet<SymbolicAddress> usedSetOut = new HashSet<SymbolicAddress>();
ObjectCacheCost cost =
computeCacheCost(persistenceSegment, getUsedRefs(persistenceSegment),
objectCache.getCostModel(), usedSetOut );
WCETTool.logger.info("O$-addMissOnceCost: "+cost.toString());
F1<SuperGraphEdge, Long> costModel = MiscUtils.const1(cost.getCost());
Set<SuperGraphEdge> costEdges = addFixedCostEdges(persistenceSegment.getEntryEdges(), ipetSolver,
costModel, KEY + "_miss_once", tag);
missCostEdges.addAll(costEdges);
}
AccessCostInfo alwaysMissAccessInfo =
extractAccessesAndCosts(alwaysMissNodes, null, objectCache.getCostModel());
missCostEdges.addAll(addStaticCost(segment, alwaysMissAccessInfo, ipetSolver));
return missCostEdges;
}
/**
* Add miss once constraints for all subsegments in the persistence cover of the given segment
* @param segment
* @param ipetSolver
* @return
* @throws LpSolveException
* @throws InvalidFlowFactException
*/
@Override
public Set<SuperGraphEdge> addMissOnceConstraints(Segment segment,
IPETSolver<SuperGraphEdge> ipetSolver) throws InvalidFlowFactException, LpSolveException {
Set<SuperGraphEdge> missEdges = new HashSet<SuperGraphEdge>();
Set<SuperGraphNode> alwaysMissNodes = new HashSet<SuperGraphNode>();
Collection<Segment> cover =
findPersistenceSegmentCover(segment, EnumSet.allOf(PersistenceCheck.class), false, alwaysMissNodes);
int segmentCounter = 0;
for(Segment persistenceSegment : cover) {
/* we need to distinguish edges which are shared between persistence segments */
String key = KEY +"_" + (++segmentCounter);
LocalPointsToResult usedRefs = getUsedRefs(persistenceSegment);
/* Compute worst-case cost */
HashSet<SymbolicAddress> usedObjectsSet = new HashSet<SymbolicAddress>();
ObjectCacheIPETModel ocim =
addObjectCacheCostEdges(persistenceSegment, usedRefs, objectCache.getCostModel(), ipetSolver);
missEdges.addAll(ocim.staticCostEdges);
missEdges.addAll(ocim.refMissEdges);
missEdges.addAll(ocim.blockMissEdges);
}
AccessCostInfo alwaysMissAccessInfo =
extractAccessesAndCosts(alwaysMissNodes, null, objectCache.getCostModel());
missEdges.addAll(addStaticCost(segment, alwaysMissAccessInfo, ipetSolver));
return missEdges;
}
@Override
public Set<SuperGraphEdge> addGlobalAllFitConstraints(Segment segment,
IPETSolver<SuperGraphEdge> ipetSolver) {
Set<SuperGraphEdge> missEdges = new HashSet<SuperGraphEdge>();
LocalPointsToResult usedRefs = getUsedRefs(segment);
/* Compute worst-case cost */
ObjectCacheIPETModel ocim =
addObjectCacheCostEdges(segment, usedRefs, objectCache.getCostModel(), ipetSolver);
missEdges.addAll(ocim.staticCostEdges);
missEdges.addAll(ocim.refMissEdges);
missEdges.addAll(ocim.blockMissEdges);
return missEdges;
}
/**
* Compute object cache cost for the given persistence segment
* @param segment the segment to consider
* @param usedRefs DFA result: the set of objects a reference might point to during one execution
* of the segment
* @param costModel The object cost model to use
* @param usedSetOut <b>out</b> set of all symbolic objects names in the segment (pass in empty set)
* @return the object cache cost for the specified segment
* @throws InvalidFlowFactException
* @throws LpSolveException
*/
private ObjectCacheCost computeCacheCost(Segment segment,
LocalPointsToResult usedRefs,
ObjectCacheCostModel costModel,
HashSet<SymbolicAddress> usedSetOut)
throws InvalidFlowFactException, LpSolveException {
/* create an ILP graph for all reachable methods */
String key = GlobalAnalysis.formatProblemName(KEY, segment.getEntryCFGs().toString());
IPETSolver<SuperGraphEdge> ipetSolver = GlobalAnalysis.buildIpetProblem(project, key, segment, new IPETConfig(project.getConfig()));
ObjectCacheIPETModel ocim = addObjectCacheCostEdges(segment, usedRefs, costModel, ipetSolver);
/* solve */
double lpCost;
Map<SuperGraphEdge, Long> flowMap = new HashMap<SuperGraphEdge,Long>();
lpCost = ipetSolver.solve(flowMap ,true);
long cost = (long) (lpCost+0.5);
return extractCost(segment, cost,flowMap, ocim.accessCostInfo, costModel, ocim.refMissEdges, ocim.blockMissEdges);
}
/**
* @param segment
* @param usedRefs
* @param costModel
* @param ipetSolver
* @return
*/
private ObjectCacheIPETModel addObjectCacheCostEdges(Segment segment,
LocalPointsToResult usedRefs, ObjectCacheCostModel costModel,
IPETSolver<SuperGraphEdge> ipetSolver) {
final AccessCostInfo accessCostInfo = extractAccessesAndCosts(segment, usedRefs, costModel);
ObjectCacheIPETModel model = new ObjectCacheIPETModel();
model.accessCostInfo = accessCostInfo;
/* cache cost edges (bypass/always miss) */
model.staticCostEdges =
addFixedCostEdges(segment.getEdges(), ipetSolver, new MiscUtils.F1<SuperGraphEdge, Long>() {
@Override
public Long apply(SuperGraphEdge v) {
return accessCostInfo.getStaticCost(v.getTarget());
}
}, KEY+"_static", 0);
/* cache cost edges (miss once) */
/* for references */
model.refMissEdges =
addPersistenceSegmentConstraints(segment, accessCostInfo.getRefAccesses(segment),
ipetSolver, MiscUtils.<SuperGraphEdge,Long>const1(costModel.getReplaceLineCost()), KEY+"_ref");
/* and for blocks */
model.blockMissEdges =
addPersistenceSegmentConstraints(segment, accessCostInfo.getBlockAccesses(segment),
ipetSolver, MiscUtils.<SuperGraphEdge,Long>const1(costModel.getLoadCacheBlockCost()), KEY+"_block");
return model;
}
/** 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 alwaysMissNodes additional node set considered to be always miss
* @return
* @throws LpSolveException
* @throws InvalidFlowFactException
*/
protected Collection<Segment> findPersistenceSegmentCover(Segment segment, EnumSet<PersistenceCheck> checks,
boolean avoidOverlap, Set<SuperGraphNode> alwaysMissNodes) throws InvalidFlowFactException, LpSolveException {
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());
}
ContextCFG entryMethod;
if(entryMethods.size() != 1) {
throw new AssertionError("findPersistenceSegmentCover: only supporting segments with unique entry method");
} else {
entryMethod = entryMethods.iterator().next();
}
if(this.isPersistenceRegion(segment, checks)) {
cover.add(segment);
} else {
/* method sub segments */
for(Pair<SuperInvokeEdge, SuperReturnEdge> invocation : segment.getCallSitesFrom(entryMethod)) {
ContextCFG callee = invocation.first().getCallee();
// System.err.println("Recursively analyzing: "+callee);
Segment subSegment = Segment.methodSegment(callee, segment.getSuperGraph());
cover.addAll(findPersistenceSegmentCover(subSegment, checks, avoidOverlap, alwaysMissNodes));
}
/* always miss nodes (not covered) */
alwaysMissNodes.addAll(segment.getNodes(entryMethod));
}
return cover;
}
/**
* @param segment
* @param checks
* @return
* @throws LpSolveException
* @throws InvalidFlowFactException
*/
private boolean isPersistenceRegion(Segment segment, EnumSet<PersistenceCheck> _checks)
throws InvalidFlowFactException, LpSolveException {
long distinctCachedTagsAccessed = countDistinctCachedTagsAccessed(contextForSegment(segment));
WCETTool.logger.info("isPersistenceRegion(): accessing "+distinctCachedTagsAccessed+" distinct tags in "+
segment+" / N="+getNumberOfWays());
return distinctCachedTagsAccessed <= getNumberOfWays();
}
protected int getNumberOfWays() {
return objectCache.getAssociativity();
}
/**
* return number of distinct cached tags which might be accessed in the given scope
* <p>XXX: use segment instead of scope</p>
* @param scope
* @return the maximum number of distinct cached tags accessed
* @throws InvalidFlowFactException
* @throws LpSolveException
*/
public long countDistinctCachedTagsAccessed(ExecutionContext scope) throws InvalidFlowFactException, LpSolveException {
Long maxCachedTags = this.maxCachedTagsAccessed.get(scope);
if(maxCachedTags != null) return maxCachedTags;
LocalPointsToResult usedRefs = getUsedRefs(scope);
/* Create an analysis segment */
Segment segment = Segment.methodSegment(scope.getMethodInfo(), scope.getCallString(),
project, project.getCallstringLength(), project);
this.saturatedTypes.put(scope, getSaturatedTypes(segment,usedRefs));
/* Compute worst-case number of objects/fields accessed */
HashSet<SymbolicAddress> usedObjectsSet = new HashSet<SymbolicAddress>();
ObjectCacheCostModel costModel;
if(this.fieldAsTag) {
costModel = ObjectCacheCostModel.COUNT_FIELD_TAGS;
} else {
costModel = ObjectCacheCostModel.COUNT_REF_TAGS;
}
maxCachedTags = computeCacheCost(segment, usedRefs, costModel, usedObjectsSet).getCost();
this.tagSet.put(scope, usedObjectsSet);
maxCachedTagsAccessed.put(scope,maxCachedTags);
return maxCachedTags;
}
/**
* @param segment
* @return the results of the symbolic address DFA for the given segment
*/
public LocalPointsToResult getUsedRefs(Segment segment) {
return getUsedRefs(contextForSegment(segment));
}
/**
* Get DFA results on symbolic reference names for the given scope
* XXX: proper segment support
* @param scope
* @return
*/
public LocalPointsToResult getUsedRefs(ExecutionContext scope) {
ExecuteOnceAnalysis eoAna = new ExecuteOnceAnalysis(project);
DFATool dfa = project.getDfaTool();
SymbolicPointsTo spt = new SymbolicPointsTo(maxSetSize,
project.getCallstringLength(),
new ExecOnceQuery(eoAna,scope));
dfa.runLocalAnalysis(spt,scope);
LocalPointsToResult lpt = new LocalPointsToResult(spt.getResult());
return lpt;
}
public Set<SymbolicAddress> getAddressSet(ExecutionContext scope) {
LocalPointsToResult lpt = getUsedRefs(scope);
return lpt.getAddressSet();
}
/** Traverse vertex set. Collect those types where we could not resolve
* the symbolic object names. (Not too useful in the analysis, but useful
* for debugging)
*/
public HashSet<String> getSaturatedTypes(Segment segment, LocalPointsToResult usedRefs) {
HashSet<String> topTypes = new HashSet<String>();
for(SuperGraphNode n : segment.getNodes()) {
BasicBlock bb = n.getCFGNode().getBasicBlock();
if(bb == null) continue;
CallString cs = n.getContextCFG().getCallString();
for(InstructionHandle ih : bb.getInstructions()) {
BoundedSet<SymbolicAddress> refs;
if(usedRefs.containsKey(ih)) {
refs = usedRefs.get(ih,cs);
String handleType = getHandleType(project, n.getCfg(), ih);
if(handleType == null) continue;
if(refs.isSaturated()) {
topTypes.add(handleType);
}
}
}
}
return topTypes;
}
/**
* Get maximum cost due to the object cache in the given scope.
* Using special cost models such as COUNT_FIELD_TAGS and COUNT_REF_TAGS, this
* method can be used to calculate different metrics as well.
* <p> XXX: Use segment instead of scope </p>
* @param scope
* @param costModel The object cache cost model
* @return
* @throws InvalidFlowFactException
* @throws LpSolveException
*/
public ObjectCacheCost getMaxCacheCost(ExecutionContext scope, ObjectCacheCostModel costModel)
throws InvalidFlowFactException, LpSolveException {
LocalPointsToResult usedRefs = getUsedRefs(scope);
Segment segment = Segment.methodSegment(scope.getMethodInfo(), scope.getCallString(),
project, project.getCallstringLength(), project);
/* Compute worst-case cost */
HashSet<SymbolicAddress> usedObjectsSet = new HashSet<SymbolicAddress>();
return computeCacheCost(segment, usedRefs, costModel, usedObjectsSet);
}
public Object getSaturatedTypes(ExecutionContext scope) throws InvalidFlowFactException, LpSolveException {
if(! this.saturatedTypes.containsKey(scope)) countDistinctCachedTagsAccessed(scope);
return this.saturatedTypes.get(scope);
}
public Set<SymbolicAddress> getUsedSymbolicNames(ExecutionContext scope) throws InvalidFlowFactException, LpSolveException {
if(! tagSet.containsKey(scope)) countDistinctCachedTagsAccessed(scope);
return tagSet.get(scope);
}
/**
*
* @param segment the segment analyzed
* @param lpCost the lp objective value
* @param flowMap the lp assignment for variables
* @param accessCostInfo information on object cache cost edges
* @param costModel the object cache cost model
* @param refMissEdges the object cache cost edges for references
* @param blockMissEdges the object cache cost edges for blocks
* @return
*/
private ObjectCache.ObjectCacheCost extractCost(Segment segment,
long lpCost,
Map<SuperGraphEdge, Long> flowMap,
AccessCostInfo accessCostInfo,
ObjectCacheCostModel costModel,
Set<SuperGraphEdge> refMissEdges,
Set<SuperGraphEdge> blockMissEdges) {
long missCount = 0; /* miss count */
long totalMissCost = 0; /* has to be equal to (cost - bypass cost) */
long bypassAccesses = 0; /* bypassed fields accesses */
long fieldAccesses = 0; /* cached fields accessed */
long totalBypassCost = 0; /* All object accesses * bypass cost */
for(SuperGraphEdge edge : segment.getEdges()) {
long edgeFreq = flowMap.get(edge);
SuperGraphNode node = edge.getTarget();
/* Compute cost for basic block */
BasicBlock bb = node.getCFGNode().getBasicBlock();
if(bb == null) continue;
long missCost = accessCostInfo.getMissCost(node) * edgeFreq;
totalMissCost += missCost;
totalBypassCost += accessCostInfo.getBypassCost(node) * edgeFreq;
/* Calculate number of unpredictable always-miss accesses, and record them */
long alwaysMissCost = costModel.getReplaceLineCost() + costModel.getLoadCacheBlockCost();
if(alwaysMissCost > 0) {
missCount += missCost / alwaysMissCost;
}
/* count normal and bypass accesses in the basic block */
for(InstructionHandle ih : bb.getInstructions()) {
String handleType = getHandleType(project, node.getCfg(), ih);
if(handleType == null) continue; /* No getfield/handle access */
if(! isFieldCached(project, node.getCfg(), ih, maxCachedFieldIndex)) {
bypassAccesses += edgeFreq;
} else {
fieldAccesses += edgeFreq;
}
}
}
/* For each miss edge, there is an associated cost; moreover
* fill-word & single-field: missCount = sum of miss block variables
* fill-line: missCount = sum of miss object reference variables
*/
long totalRefMisses = 0;
for(SuperGraphEdge refMissEdge : refMissEdges) {
totalRefMisses += flowMap.get(refMissEdge);
}
totalMissCost += costModel.getReplaceLineCost() * totalRefMisses;
long totalBlockMisses = 0;
for(SuperGraphEdge blockMissEdge : blockMissEdges) {
totalBlockMisses += flowMap.get(blockMissEdge);
}
totalMissCost += costModel.getLoadCacheBlockCost() * totalBlockMisses;
missCount += totalBlockMisses;
if(totalMissCost + totalBypassCost != lpCost) {
WCETTool.logger.warn(String.format("Error in calculating missCost in all fit-area (misscount = %d): %d but should be %d (%d - %d)",
missCount, totalMissCost,lpCost-totalBypassCost,lpCost,totalBypassCost));
}
ObjectCache.ObjectCacheCost ocCost = new ObjectCache.ObjectCacheCost(missCount, totalMissCost,bypassAccesses, totalBypassCost, fieldAccesses);
return ocCost;
}
/* Helpers */
/* ------- */
/** Traverse vertex set.
* <p>Add vertex to access set of referenced addresses
* For references whose type cannot be fully resolved, add a
* cost of 1.</p>
* <p>FIXME: We should deal with subtyping (or better use storage based alias-analysis)</p>
*
* @param segment
* @param usedRefs the results of the local points-to analysis, or {@code null} for always miss costs
* @param costModel
*/
private AccessCostInfo extractAccessesAndCosts(
Segment segment,
LocalPointsToResult usedRefs,
ObjectCacheCostModel costModel) {
return extractAccessesAndCosts(segment.getNodes(), usedRefs, costModel);
}
/** Traverse vertex set.
* <p>Add vertex to access set of referenced addresses
* For references whose type cannot be fully resolved, add a
* cost of 1.</p>
* <p>FIXME: We should deal with subtyping (or better use storage based alias-analysis)</p>
*
* @param nodes
* @param usedRefs the results of the local points-to analysis, or {@code null} for always miss costs
* @param costModel
*/
private AccessCostInfo extractAccessesAndCosts(
Iterable<SuperGraphNode> nodes, LocalPointsToResult usedRefs,
ObjectCacheCostModel costModel) {
AccessCostInfo aci = new AccessCostInfo();
for(SuperGraphNode node : nodes) {
/* Compute cost for basic block */
BasicBlock bb = node.getCFGNode().getBasicBlock();
if(bb == null) continue;
long bypassCost = 0;
long alwaysMissCost = 0;
CallString cs = node.getContextCFG().getCallString();
for(InstructionHandle ih : bb.getInstructions()) {
String handleType = getHandleType(project, node.getCfg(), ih);
if(handleType == null) continue; /* No getfield/handle access */
int fieldIndex = getFieldIndex(project, node.getCfg(), ih);
int blockIndex = getBlockIndex(fieldIndex);
// System.err.println("Processing getfield "+ih+ " with field type "+getCachedType(project, node.getCfg(), ih));
if(fieldIndex > this.maxCachedFieldIndex) {
bypassCost += costModel.getFieldAccessCostBypass();
continue;
}
BoundedSet<SymbolicAddress> refs = null;
if(usedRefs != null) {
if(! usedRefs.containsKey(ih)) {
usedRefs = null;
WCETTool.logger.error("No DFA results for: "+ih.getInstruction() +
" with field " + ((FieldInstruction)ih.getInstruction()).getFieldName(bb.cpg()));
} else {
refs = usedRefs.get(ih,cs);
if(refs.isSaturated()) refs = null;
}
}
if(refs == null) {
alwaysMissCost += costModel.getReplaceLineCost() + costModel.getLoadCacheBlockCost();
} else {
for(SymbolicAddress ref : refs.getSet()) {
aci.addRefAccess(ref,node);
aci.addBlockAccess(ref.accessArray(blockIndex), node);
// Handle getfield_long / getfield_double
if(getCachedType(project, node.getCfg(), ih) == Type.LONG ||
getCachedType(project, node.getCfg(), ih) == Type.DOUBLE) {
if(blockIndex+1 > this.maxCachedFieldIndex) {
bypassCost += costModel.getFieldAccessCostBypass();
} else {
aci.addBlockAccess(ref.accessArray(blockIndex+1), node);
}
}
}
}
}
aci.putBypassCost(node, bypassCost);
aci.putStaticCost(node, bypassCost + alwaysMissCost);
}
return aci;
}
/**
* XXX: temporary helper to bridge gap between two representations (segment and scope)
* @param segment
* @return the corresponding execution context
* @throws RuntimeException if the conversion is impossible
*/
private ExecutionContext contextForSegment(Segment segment) {
ContextCFG entry;
Set<ContextCFG> entries = segment.getEntryCFGs();
if(entries.size() != 1) {
throw new RuntimeException("contextForSegment(): Currently we only support a single entry method");
}
entry = entries.iterator().next();
return new ExecutionContext(entry.getCfg().getMethodInfo(), entry.getCallString());
}
private int getBlockIndex(int fieldIndex) {
return fieldIndex >> blockIndexBits;
}
/** Get all access sites per method */
public static Map<MethodInfo, Set<SuperGraph.SuperGraphEdge>> getAccessEdges(SuperGraph sg) {
Map<MethodInfo, Set<SuperGraph.SuperGraphEdge>> accessEdges =
new HashMap<MethodInfo, Set<SuperGraph.SuperGraphEdge>>();
for(Entry<SuperGraph.SuperInvokeEdge, SuperGraph.SuperReturnEdge> invokeSite: sg.getSuperEdgePairs().entrySet()) {
MethodInfo invoked = invokeSite.getKey().getInvokeNode().receiverFlowGraph().getMethodInfo();
addToSet(accessEdges, invoked, invokeSite.getKey());
MethodInfo invoker = invokeSite.getKey().getInvokeNode().invokerFlowGraph().getMethodInfo();
addToSet(accessEdges, invoker, invokeSite.getValue());
}
return accessEdges;
}
/**
* @return the index of the field accessed by the instruction, or 0 if the instruction
* does not access a field
*/
private static int getFieldIndex(WCETTool p, ControlFlowGraph cfg, InstructionHandle ih) {
ConstantPoolGen constPool = cfg.getMethodInfo().getConstantPoolGen();
Instruction instr = ih.getInstruction();
if(instr instanceof FieldInstruction) {
FieldInstruction fieldInstr = (FieldInstruction) instr;
ReferenceType refType = fieldInstr.getReferenceType(constPool);
if(!(refType instanceof ObjectType)) {
throw new RuntimeException("getFieldIndex(): Unsupported object kind: "+refType.getClass());
}
ObjectType objType = (ObjectType)refType;
String klassName = objType.getClassName();
String fieldName = fieldInstr.getFieldName(constPool);
String fieldSig = fieldInstr.getSignature(constPool);
return p.getLinkerInfo().getFieldIndex(klassName, fieldName+fieldSig);
} else {
return 0;
}
}
/**
* @param maxCachedFieldIndex
* @return whether the field accessed by the given instruction handle is cached
*/
public static boolean isFieldCached(WCETTool project, ControlFlowGraph cfg, InstructionHandle ih, int maxCachedFieldIndex) {
int index = ObjectCacheAnalysis.getFieldIndex(project, cfg,ih);
/* Uncached fields are treated separately */
return (index <= maxCachedFieldIndex);
}
public static Type getCachedType(WCETTool project, ControlFlowGraph cfg, InstructionHandle ih) {
ConstantPoolGen constPool = cfg.getMethodInfo().getConstantPoolGen();
Instruction instr = ih.getInstruction();
if(instr instanceof GETFIELD) {
GETFIELD gf = (GETFIELD) instr;
return gf.getFieldType(constPool);
}
if(! ALL_HANDLE_ACCESSES) {
return null;
} else {
throw new AssertionError("For O$, only getfield is supported right now");
}
}
public static String getHandleType(WCETTool project, ControlFlowGraph cfg, InstructionHandle ih) {
ConstantPoolGen constPool = cfg.getMethodInfo().getConstantPoolGen();
Instruction instr = ih.getInstruction();
if(instr instanceof GETFIELD) {
GETFIELD gf = (GETFIELD) instr;
ReferenceType refty = gf.getReferenceType(constPool);
return refty.toString();
}
if(! ALL_HANDLE_ACCESSES)
return null;
if(instr instanceof PUTFIELD) {
PUTFIELD pf = (PUTFIELD) instr;
ReferenceType refty = pf.getReferenceType(constPool);
return refty.toString();
}
if(instr instanceof ArrayInstruction)
{
//ArrayInstruction ainstr = (ArrayInstruction) instr;
return "[]";
}
if(instr instanceof ARRAYLENGTH) {
//ARRAYLENGTH ainstr = (ARRAYLENGTH) instr;
return "[]";
}
if(instr instanceof INVOKEINTERFACE || instr instanceof INVOKEVIRTUAL)
{
return "$header";
}
return null;
}
}