/*
* 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;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
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 com.jopdesign.common.MethodInfo;
import com.jopdesign.common.code.ControlFlowGraph;
import com.jopdesign.common.code.ControlFlowGraph.BasicBlockNode;
import com.jopdesign.common.code.ControlFlowGraph.CFGEdge;
import com.jopdesign.common.code.ControlFlowGraph.CFGNode;
import com.jopdesign.common.code.ControlFlowGraph.ReturnNode;
import com.jopdesign.common.code.ExecutionContext;
import com.jopdesign.common.code.LoopBound;
import com.jopdesign.common.code.Segment;
import com.jopdesign.common.code.SuperGraph.ContextCFG;
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.code.SymbolicMarker;
import com.jopdesign.common.code.SymbolicMarker.SymbolicMarkerType;
import com.jopdesign.common.graphutils.LoopColoring;
import com.jopdesign.common.graphutils.Pair;
import com.jopdesign.common.misc.AppInfoError;
import com.jopdesign.common.misc.Iterators;
import com.jopdesign.common.misc.MiscUtils;
import com.jopdesign.wcet.WCETTool;
import com.jopdesign.wcet.analysis.RecursiveAnalysis.RecursiveStrategy;
import com.jopdesign.wcet.analysis.cache.CacheAnalysis;
import com.jopdesign.wcet.analysis.cache.CacheAnalysis.UnsupportedCacheModelException;
import com.jopdesign.wcet.analysis.cache.CachePersistenceAnalysis.PersistenceCheck;
import com.jopdesign.wcet.analysis.cache.MethodCacheAnalysis;
import com.jopdesign.wcet.analysis.cache.SuperGraphExtraCostEdge;
import com.jopdesign.wcet.annotations.LoopBoundExpr;
import com.jopdesign.wcet.ipet.IPETConfig;
import com.jopdesign.wcet.ipet.IPETConfig.CacheCostCalculationMethod;
import com.jopdesign.wcet.ipet.IPETSolver;
import com.jopdesign.wcet.ipet.IPETUtils;
import com.jopdesign.wcet.ipet.LinearConstraint;
import com.jopdesign.wcet.ipet.LinearConstraint.ConstraintType;
import com.jopdesign.wcet.jop.CacheModel;
/**
* Global IPET-based analysis, supporting variable block caches (all fit region approximation).
*
* @author Benedikt Huber <benedikt.huber@gmail.com>
*/
public class GlobalAnalysis {
private WCETTool project;
private IPETConfig ipetConfig;
public GlobalAnalysis(WCETTool p, IPETConfig ipetConfig) {
this.ipetConfig = ipetConfig;
this.project = p;
}
/**
* Compute WCET using global IPET, and either ALWAYS_MISS or GLOBAL_ALL_FIT
*/
public WcetCost computeWCET(MethodInfo m, AnalysisContextLocal ctx) throws Exception {
CacheCostCalculationMethod cacheMode = ctx.getCacheApproxMode();
String key = "global" + "_" + cacheMode;
Segment segment = Segment.methodSegment(m, ctx.getCallString(),project, project.getAppInfo().getCallstringLength(), project);
return computeWCET(key, segment, cacheMode);
}
/**
* Compute WCET for a segment, using global IPET, and cache analysis results
* @throws InvalidFlowFactException
* @throws LpSolveException
* @throws UnsupportedCacheModelException
*/
public WcetCost computeWCET(String key, Segment segment, CacheCostCalculationMethod cacheMode) throws InvalidFlowFactException, LpSolveException, UnsupportedCacheModelException {
/* create an IPET problem for the segment */
String problemId = formatProblemName(key, segment.getEntryCFGs().toString());
IPETSolver<SuperGraphEdge> ipetSolver = buildIpetProblem(project, problemId, segment, ipetConfig);
/* compute cost */
setExecutionCost(segment, ipetSolver);
/* Add constraints for caches */
HashMap<String, Set<SuperGraphEdge>> costMissEdges = new HashMap<String, Set<SuperGraphEdge>>();
for(CacheModel cacheModel : project.getWCETProcessorModel().getCaches()) {
CacheAnalysis cpa = CacheAnalysis.getCacheAnalysisFor(cacheModel, project);
Set<SuperGraphEdge> edges = cpa.addCacheCost(segment, ipetSolver, cacheMode);
costMissEdges.put(cacheModel.toString(), edges);
}
/* Add constraints for object cache */
// ObjectRefAnalysis objectCacheAnalysis = new ObjectRefAnalysis(project, false, 4, 8, 4);
/* Return variables */
Map<SuperGraphEdge, Long> flowMap = new HashMap<SuperGraphEdge, Long>();
/* Solve */
long _start = System.currentTimeMillis();
double relaxedCost = 0;
try {
relaxedCost = ipetSolver.solve(null, false);
} catch(LpSolveException ex) {
WCETTool.logger.error("Solving the relaxed problem failed - bug in lp solving lib?");
}
long _time_rlp = System.currentTimeMillis() - _start;
double ilpCost = ipetSolver.solve(flowMap);
long _time_ilp = System.currentTimeMillis() - _start;
WCETTool.logger.info(String.format("LP (%d ms) %d | %d ILP (%d ms)",_time_rlp, Math.round(relaxedCost), Math.round(ilpCost), _time_ilp));
/* Cost extraction */
WcetCost cost;
/* extract cost and generate a profile in 'profiles' */
cost = exportCostProfile(flowMap, costMissEdges, ipetSolver, problemId);
/* Sanity Check, and Return */
if(Double.isInfinite(ilpCost)) {
throw new AssertionError("[GlobalAnalysis] Unbounded (infinite lp cost)");
}
long objValue = (long) (ilpCost + 0.5);
if (cost.getCost() != objValue) {
throw new AssertionError("[GlobalAnalysis] Inconsistency: lpValue vs. extracted value: " + objValue + " / " + cost.getCost());
}
return cost;
}
/**
* Create an interprocedural max-cost max-flow problem for the given segment<br/>
* Notes:<ul>
* <li/> super graph edges always have the callstring of the invoking method
* </ul>
*
* @param wcetTool A reference to the WCETTool
* @param problemName A unique identifier for the problem (for reporting)
* @param segment The segment to build the ILP for
* @param ipetConfig Cost of nodes (or {@code null} if no cost is associated with nodes)
* @return The max-cost maxflow problem
* @throws InvalidFlowFactException
*/
public static IPETSolver<SuperGraphEdge> buildIpetProblem(WCETTool wcetTool, String problemName, Segment segment, IPETConfig ipetConfig) throws InvalidFlowFactException {
IPETSolver<SuperGraphEdge> ipetSolver = new IPETSolver<SuperGraphEdge>(problemName, ipetConfig);
/* DEBUGGING: Render segment */
// try {
// segment.exportDOT(wcetTool.getProjectConfig().getOutFile(problemName+".dot"));
// } catch (IOException e) {
// e.printStackTrace();
// }
/* In- and Outflow */
ipetSolver.addConstraint(IPETUtils.constantFlow(segment.getEntryEdges(), 1));
ipetSolver.addConstraint(IPETUtils.constantFlow(segment.getExitEdges(), 1));
/* Structural flow constraints */
for (SuperGraphNode node: segment.getNodes()) {
ipetSolver.addConstraint(IPETUtils.flowPreservation(segment.incomingEdgesOf(node), segment.outgoingEdgesOf(node)));
}
/* Supergraph constraints */
for (Pair<SuperInvokeEdge, SuperReturnEdge> superEdgePair : segment.getCallSites()) {
Iterable<SuperGraphEdge> es1 = Iterators.<SuperGraphEdge>singleton(superEdgePair.first());
Iterable<SuperGraphEdge> es2 = Iterators.<SuperGraphEdge>singleton(superEdgePair.second());
ipetSolver.addConstraint(IPETUtils.flowPreservation(es1,es2));
}
/* Program Flow Constraints */
for(LinearConstraint<SuperGraphEdge> flowFact : getFlowFacts(wcetTool, segment)) {
ipetSolver.addConstraint(flowFact);
}
return ipetSolver;
}
/**
* Get all flow facts (e.g. loop bounds, infeasible edges) for the given segment
* @param segment
* @return
* @throws InvalidFlowFactException
*/
private static Iterable<LinearConstraint<SuperGraphEdge>> getFlowFacts(
WCETTool wcetTool, Segment segment) throws InvalidFlowFactException {
return Iterators.concat(
getLoopBounds(wcetTool, segment),
getInfeasibleEdgeConstraints(wcetTool, segment));
}
/**
* <p>Get all loop bounds for the given segment.</p>
* <p>For each loop bound B for loop H relative to marker M:</p>
* <p>sum(M) * B <= sum(continue-edges-of(H))</p>
*
* @param segment
* @return
* @throws InvalidFlowFactException
*/
private static Iterable<LinearConstraint<SuperGraphEdge>> getLoopBounds(
WCETTool wcetTool, Segment segment) throws InvalidFlowFactException {
List<LinearConstraint<SuperGraphEdge>> constraints =
new ArrayList<LinearConstraint<SuperGraphEdge>>();
// For all CFG instances
for(ContextCFG ccfg : segment.getCallGraphNodes()) {
ControlFlowGraph cfg = ccfg.getCfg();
// for all loops in the method
LoopColoring<CFGNode, ControlFlowGraph.CFGEdge> loops = cfg.getLoopColoring();
for (CFGNode hol : loops.getHeadOfLoops()) {
LoopBound loopBound = wcetTool.getLoopBound(hol, ccfg.getContext().getCallString());
if (loopBound == null) {
throw new AppInfoError("No loop bound record for head of loop: " + hol + " : " + cfg.buildLoopBoundMap());
}
addLoopConstraints(constraints, segment, ccfg, hol, loops, loopBound);
}
}
return constraints;
}
/**
* Add loop contraints
* @param constraints the new constraints are added to this collection
* @param segment
* @param ccfg
* @param headOfLoop
* @param loops
* @param loopBound
* @throws InvalidFlowFactException
*/
private static void addLoopConstraints(
List<LinearConstraint<SuperGraphEdge>> constraints,
Segment segment,
ContextCFG ccfg,
CFGNode headOfLoop,
LoopColoring<CFGNode, CFGEdge> loops,
LoopBound loopBound) throws InvalidFlowFactException {
/* marker loop constraints */
for (Entry<SymbolicMarker, LoopBoundExpr> markerBound : loopBound.getLoopBounds()) {
/* loop constraint */
LinearConstraint<SuperGraphEdge> loopConstraint =
new LinearConstraint<SuperGraphEdge>(ConstraintType.GreaterEqual);
/* rhs = sum(continue-edges(loop)) */
Iterable<SuperGraphEdge> continueEdges =
segment.liftCFGEdges(ccfg, loops.getBackEdgesTo(headOfLoop));
loopConstraint.addRHS( continueEdges );
/* Multiplicities */
ExecutionContext executionContext =
new ExecutionContext(ccfg.getCfg().getMethodInfo(), ccfg.getCallString());
long lhsMultiplicity = markerBound.getValue().upperBound(executionContext);
SymbolicMarker marker = markerBound.getKey();
if (marker.getMarkerType() == SymbolicMarkerType.OUTER_LOOP_MARKER) {
CFGNode outerLoopHol;
outerLoopHol = loops.getLoopAncestor(headOfLoop, marker.getOuterLoopDistance());
if (outerLoopHol == null) {
throw new InvalidFlowFactException("Bad outer loop annotation");
}
Iterable<SuperGraphEdge> exitEdges =
segment.liftCFGEdges(ccfg, loops.getExitEdgesOf(outerLoopHol));
for (SuperGraphEdge exitEdge : exitEdges) {
loopConstraint.addLHS(exitEdge, lhsMultiplicity);
}
} else {
assert (marker.getMarkerType() == SymbolicMarkerType.METHOD_MARKER);
throw new AssertionError("ILPModelBuilder: method markers not yet supported, sorry");
}
constraints.add(loopConstraint);
}
}
/**
* For each infeasible edge, assert that the edge has flow 0
* @param wcetTool
* @param segment
* @return
*/
private static Iterable<LinearConstraint<SuperGraphEdge>> getInfeasibleEdgeConstraints(
WCETTool wcetTool, Segment segment) {
List<LinearConstraint<SuperGraphEdge>> constraints = new ArrayList<LinearConstraint<SuperGraphEdge>>();
// - for each infeasible edge
// -- edge = 0
for(ContextCFG ccfg : segment.getCallGraphNodes()) {
for (CFGEdge edge : wcetTool.getInfeasibleEdges(ccfg.getCfg(), ccfg.getCallString())) {
LinearConstraint<SuperGraphEdge> infeasibleConstraint =
new LinearConstraint<SuperGraphEdge>(ConstraintType.Equal);
infeasibleConstraint.addLHS(segment.liftCFGEdges(ccfg, Iterators.singleton(edge)));
infeasibleConstraint.addRHS(0);
constraints.add(infeasibleConstraint);
}
}
return constraints;
}
/**
* Compute the execution time of each edge in in the supergraph
*
* @param segment the supergraph, whose vertices are considered
* @param ipetInst the IPET instance
* @return the cost map
*/
private Map<SuperGraphEdge, WcetCost> setExecutionCost(Segment segment, IPETSolver<SuperGraphEdge> ipetInstance) {
HashMap<SuperGraphEdge, WcetCost> edgeCost = new HashMap<SuperGraphEdge, WcetCost>();
/* Attribute edge cost to edge source */
for (SuperGraphEdge e : segment.getEdges()) {
/* ignore exit edges, because their target is per definitionem not part of the segment */
if(segment.isExitEdge(e)) continue;
SuperGraphNode sg = e.getTarget();
WcetCost cost = calculateCost(sg);
edgeCost.put(e, cost);
ipetInstance.addEdgeCost(e, cost.getCost());
}
return edgeCost;
}
/**
* @param flowMap
* @param costMissEdges
* @param ipetSolver
* @param problemId
*/
private WcetCost exportCostProfile(Map<SuperGraphEdge, Long> flowMap,
HashMap<String, Set<SuperGraphEdge>> costMissEdges,
IPETSolver<SuperGraphEdge> ipetSolver,
String problemId) {
File profileFile = new File(project.getOutDir("profiles"),problemId+".txt");
WcetCost cost = new WcetCost();
HashMap<SuperGraphEdge,Long> costProfile = new HashMap<SuperGraphEdge,Long>();
HashMap<SuperGraphEdge,Long> cacheCostProfile = new HashMap<SuperGraphEdge,Long>();
/* extra cost lookup map */
HashMap<SuperGraphEdge, String> extraCostKeys = new HashMap<SuperGraphEdge, String>();
for(Entry<String, Set<SuperGraphEdge>> cacheEntry: costMissEdges.entrySet()) {
String key = cacheEntry.getKey();
for(SuperGraphEdge costEdge : cacheEntry.getValue()) {
extraCostKeys.put(costEdge, key);
}
}
for (Entry<SuperGraphEdge, Long> flowEntry : flowMap.entrySet()) {
SuperGraphEdge edge = flowEntry.getKey();
long edgeCost = ipetSolver.getEdgeCost(edge);
long flowCost = edgeCost * flowEntry.getValue();
if(extraCostKeys.containsKey(edge)) {
cost.addCacheCost(extraCostKeys.get(edge), flowCost);
MiscUtils.incrementBy(cacheCostProfile, edge, flowCost, 0);
} else {
cost.addLocalCost(flowCost);
}
MiscUtils.incrementBy(costProfile, edge, flowCost, 0);
}
/* export profile */
try {
ArrayList<Entry<SuperGraphEdge,Long>> profile = new ArrayList<Entry<SuperGraphEdge,Long>>(costProfile.entrySet());
Collections.sort(profile, new Comparator<Entry<SuperGraphEdge,Long>>() {
@Override
public int compare(Entry<SuperGraphEdge, Long> o1, Entry<SuperGraphEdge, Long> o2) {
return o2.getValue().compareTo(o1.getValue());
}
});
FileWriter fw = new FileWriter(profileFile);
fw.append("Profile\n");
fw.append(problemId+"\n");
fw.append("------------------------------------------------------\n");
long totalCost = cost.getCost();
for(Entry<SuperGraphEdge, Long> entry : profile) {
Long flowCost = entry.getValue();
double contribution = (100.0 * flowCost) / totalCost;
fw.append(String.format(" %-50s %8d %.2f%%",entry.getKey(), flowCost,contribution));
if(cacheCostProfile.containsKey(entry.getKey())) {
fw.append(String.format(" cache{%d}",cacheCostProfile.get(entry.getKey())));
}
fw.append(String.format(" flow{%d}",flowMap.get(entry.getKey())));
fw.append('\n');
}
fw.close();
} catch (IOException ex) {
WCETTool.logger.error("Generating profile file failed: "+ex);
}
return cost;
}
// FIXME: Remove? I think it is not needed any more
@Deprecated
public static class GlobalIPETStrategy
implements RecursiveStrategy<AnalysisContextLocal, WcetCost> {
private IPETConfig ipetConfig;
public GlobalIPETStrategy(IPETConfig ipetConfig) {
this.ipetConfig = ipetConfig;
}
public WcetCost recursiveCost(
RecursiveAnalysis<AnalysisContextLocal, WcetCost> stagedAnalysis,
ControlFlowGraph.InvokeNode n,
AnalysisContextLocal ctx) {
if (ctx.getCacheApproxMode() != CacheCostCalculationMethod.ALL_FIT_REGIONS) {
throw new AssertionError("Cache Mode " + ctx.getCacheApproxMode() + " not supported using" +
" _mixed_ local/global IPET strategy");
}
WCETTool project = stagedAnalysis.getWCETTool();
MethodCacheAnalysis mca = new MethodCacheAnalysis(project);
int callStringLength = project.getCallstringLength();
MethodInfo invoker = n.getBasicBlock().getMethodInfo();
MethodInfo invoked = n.getImplementingMethod();
long returnCost = mca.getMissOnceCost(invoker, false);
long invokeReturnCost = mca.getInvokeReturnMissCost(n.getInvokeSite(), ctx.getCallString());
WcetCost cost = new WcetCost();
AnalysisContextLocal recCtx = ctx.withCallString(ctx.getCallString().push(n, callStringLength));
Segment segment = Segment.methodSegment(invoked, recCtx.getCallString(), project, callStringLength, project);
if (!project.getCallGraph().isLeafMethod(invoked) &&
mca.isPersistenceRegion(segment,EnumSet.allOf(PersistenceCheck.class))) {
/* Perform a GLOBAL-ALL-FIT analysis */
GlobalAnalysis ga = new GlobalAnalysis(project, ipetConfig);
WcetCost allFitCost = null;
try {
allFitCost = ga.computeWCET(invoked, recCtx.withCacheApprox(CacheCostCalculationMethod.GLOBAL_ALL_FIT));
}
catch (Exception e) {
throw new AssertionError(e);
}
cost.addCacheCost(returnCost + allFitCost.getCacheCost());
cost.addNonLocalCost(allFitCost.getNonCacheCost());
cost.addPotentialCacheFlushes(1);
//System.err.println("Potential cache flush: "+invoked+" from "+invoker);
} else {
WcetCost recCost = stagedAnalysis.computeCost(invoked, recCtx);
cost.addCacheCost(recCost.getCacheCost() + invokeReturnCost);
cost.addNonLocalCost(recCost.getCost() - recCost.getCacheCost());
}
WCETTool.logger.debug("Recursive WCET computation [GLOBAL IPET]: " + invoked +
". cummulative cache cost: " + cost.getCacheCost() +
", execution cost: " + cost.getNonCacheCost());
return cost;
}
}
/* cost calculation */
private WcetCost calculateCost(SuperGraphNode sgn) {
AnalysisContext ctx = new AnalysisContextCallString(sgn.getContextCFG().getCallString());
WcetVisitor costCalculator = new BasicBlockCost(project, ctx);
return costCalculator.computeCost(sgn.getCFGNode());
}
private static class BasicBlockCost extends WcetVisitor {
private AnalysisContext ctx;
public BasicBlockCost(WCETTool p, AnalysisContext ctx) {
super(p);
this.ctx = ctx;
}
@Override
public void visitInvokeNode(ControlFlowGraph.InvokeNode n) {
visitBasicBlockNode(n);
}
@Override
public void visitReturnNode(ReturnNode n) {
}
@Override
public void visitBasicBlockNode(BasicBlockNode n) {
cost.addLocalCost(project.getWCETProcessorModel().basicBlockWCET(ctx.getExecutionContext(n), n.getBasicBlock()));
}
}
/**
* Generate a suitable 'problem name' for reporting (unique, no longer than 80 characters)
* @param key The key of the problem generator (e.g. method-cache-analysis). <b>Has to have less than 60 characters</b>
* @param description A (potentially long) description
* @return A string {@code key + '_' + unique(key) + '_' + description}, truncated so has at most 80 characters (filenames!)
*/
public static String formatProblemName(String key, String description) {
if(key.length() > 60) throw new AssertionError("formatProblemName: precondition violation: |key|>60");
StringBuffer sb = new StringBuffer();
sb.append(key);
sb.append('_');
sb.append(generateProblemId(key));
sb.append('_');
sb.append(description);
return sb.substring(0, Math.min(80, sb.length()));
}
private static Map<String,Long> problemCounter = new HashMap<String,Long>();
private static long generateProblemId(String key) {
return MiscUtils.increment(problemCounter,key,0);
}
}