/*
* 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.MethodCode;
import com.jopdesign.common.MethodInfo;
import com.jopdesign.common.code.CallGraph;
import com.jopdesign.common.code.CallGraph.ContextEdge;
import com.jopdesign.common.code.CallGraph.DUMPTYPE;
import com.jopdesign.common.code.ControlFlowGraph;
import com.jopdesign.common.code.ControlFlowGraph.BasicBlockNode;
import com.jopdesign.common.code.ControlFlowGraph.CFGNode;
import com.jopdesign.common.code.ControlFlowGraph.InvokeNode;
import com.jopdesign.common.code.ExecutionContext;
import com.jopdesign.common.code.InvokeSite;
import com.jopdesign.common.config.Config;
import com.jopdesign.common.config.Config.BadConfigurationException;
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.NodeVisitor;
import com.jopdesign.common.graphutils.TopologicalTraverser;
import com.jopdesign.jcopter.JCopter;
import com.jopdesign.wcet.ProjectConfig;
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.RecursiveStrategy;
import com.jopdesign.wcet.analysis.RecursiveWcetAnalysis;
import com.jopdesign.wcet.analysis.WcetCost;
import com.jopdesign.wcet.ipet.IPETConfig;
import com.jopdesign.wcet.ipet.IPETConfig.CacheCostCalculationMethod;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.log4j.Logger;
import org.jgrapht.DirectedGraph;
import org.jgrapht.traverse.TopologicalOrderIterator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
/**
* @author Stefan Hepp (stefan@stefant.org)
*/
public class WCAInvoker extends ExecFrequencyProvider {
private final JCopter jcopter;
private final Set<MethodInfo> wcaTargets;
private final AnalysisManager analyses;
private final Map<ExecutionContext, Map<CFGNode,Long>> wcaNodeFlow;
private final Map<MethodInfo,Long> execCounts;
private WCETTool wcetTool;
private RecursiveWcetAnalysis<AnalysisContextLocal> recursiveAnalysis;
private CacheCostCalculationMethod cacheApproximation;
private boolean provideWCAExecCount;
private long lastWCET;
private static final Logger logger = Logger.getLogger(JCopter.LOG_ANALYSIS+".WCAInvoker");
public WCAInvoker(AnalysisManager analyses, Set<MethodInfo> wcaTargets, CacheCostCalculationMethod defaultApproximation) {
this.analyses = analyses;
this.jcopter = analyses.getJCopter();
this.wcaTargets = wcaTargets;
cacheApproximation = defaultApproximation;
wcetTool = jcopter.getWcetTool();
wcaNodeFlow = new LinkedHashMap<ExecutionContext, Map<CFGNode, Long>>();
execCounts = new LinkedHashMap<MethodInfo, Long>();
}
public JCopter getJcopter() {
return jcopter;
}
public Set<MethodInfo> getWcaTargets() {
return wcaTargets;
}
public Collection<CallGraph> getWCACallGraphs() {
return Collections.singleton(wcetTool.getCallGraph());
}
public long getLastWCET() {
return lastWCET;
}
///////////////////////////////////////////////////////////////////////////////
// Init WCA results, lookup WCA results
///////////////////////////////////////////////////////////////////////////////
public void initTool() throws BadConfigurationException {
if (wcaTargets.isEmpty()) {
throw new BadConfigurationException("No WCA target method is given!");
}
if (wcaTargets.size() != 1) {
// TODO To support this, we would either need to split the WCA tool into the tool itself
// (which does configuration stuff and holds global results) and a Project class per target
// which represents the analysis for one target which holds the wcet-callgraph and is passed
// to all analyses,
// or we would need to rerun the WCA for each target every time a method is optimized,
// or we would need to support multiple roots for the WCETTool callgraph (and its analyses)
throw new BadConfigurationException("Currently only a single WCA target is supported.");
}
setWCETOptions(wcaTargets.iterator().next(), false);
// Init WCA tool
wcetTool.initialize(false, false);
}
public void initAnalysis(boolean useMethodCacheStrategy) {
IPETConfig ipetConfig = new IPETConfig(wcetTool.getConfig());
RecursiveStrategy<AnalysisContextLocal,WcetCost> strategy;
if (useMethodCacheStrategy) {
strategy = analyses.getMethodCacheAnalysis().createRecursiveStrategy(wcetTool, ipetConfig, cacheApproximation);
} else {
if (cacheApproximation.needsInterProcIPET()) {
strategy = new GlobalAnalysis.GlobalIPETStrategy(ipetConfig);
} else {
strategy = new LocalAnalysis(wcetTool, ipetConfig);
}
}
recursiveAnalysis = new RecursiveWcetAnalysis<AnalysisContextLocal>(
wcetTool, ipetConfig, strategy);
// Perform initial analysis
runAnalysis(wcetTool.getCallGraph().getReversedGraph());
updateWCEP();
}
public boolean isWCAMethod(MethodInfo method) {
return wcetTool.getCallGraph().containsMethod(method);
}
public boolean isOnLocalWCETPath(MethodInfo method, InstructionHandle ih) {
ControlFlowGraph cfg = method.getCode().getControlFlowGraph(false);
BasicBlockNode block = cfg.getHandleNode(ih, true);
// we do not have a block.. this is some exception handling path (hopefully..)
if (block == null) {
return false;
}
for (ExecutionContext node : wcetTool.getCallGraph().getNodes(method)) {
Long flow = wcaNodeFlow.get(node).get(block);
if (flow > 0) return true;
}
return false;
}
///////////////////////////////////////////////////////////////////////////////
// Lookup on global WCET path, WCET-path exec counts of methods
///////////////////////////////////////////////////////////////////////////////
public boolean doProvideWCAExecCount() {
return provideWCAExecCount;
}
public void setProvideWCAExecCount(boolean provideWCAExecCount) {
this.provideWCAExecCount = provideWCAExecCount;
}
public boolean isOnWCETPath(MethodInfo method) {
return getExecCount(method) > 0;
}
@Override
public long getExecCount(MethodInfo method) {
Long cnt = execCounts.get(method);
return cnt != null ? cnt : 0;
}
@Override
public long getExecFrequency(InvokeSite invokeSite, MethodInfo invokee) {
// if the CFG has been devirtualized, we can look up the frequency for a given invoke directly
if (!invokeSite.isJVMCall()) {
ControlFlowGraph cfg = invokeSite.getInvoker().getCode().getControlFlowGraph(false);
for (CFGNode node : cfg.getGraph().vertexSet()) {
if (!(node instanceof InvokeNode)) continue;
InvokeNode inv = (InvokeNode) node;
if (invokee.equals(inv.getImplementingMethod())) {
return getExecFrequency(invokeSite.getInvoker(), inv);
}
}
}
// else we fall back to the virtual invoke node
return getExecFrequency(invokeSite);
}
@Override
public long getExecFrequency(MethodInfo method, InstructionHandle ih) {
ControlFlowGraph cfg = method.getCode().getControlFlowGraph(false);
BasicBlockNode block = cfg.getHandleNode(ih);
return getExecFrequency(method, block);
}
public long getExecFrequency(MethodInfo method, CFGNode block) {
long flow = 0;
for (ExecutionContext node : wcetTool.getCallGraph().getNodes(method)) {
Long value = wcaNodeFlow.get(node).get(block);
flow += value;
}
return flow;
}
@Override
public Set<MethodInfo> getChangeSet() {
// If this is used as exec count provider, everything is recalculated, so no need to keep track of changes
return Collections.emptySet();
}
///////////////////////////////////////////////////////////////////////////////
// Update results
///////////////////////////////////////////////////////////////////////////////
/**
* Update the WCA results after a set of methods have been changed. The changesets of analyses
* in the AnalysisManager are checked for changes too.
*
* @param changedMethods a set of methods of which the code has been modified.
* @return a set of all methods for which the path may have changed.
*/
public Set<MethodInfo> updateWCA(Collection<MethodInfo> changedMethods) {
// Now we need to clear all results for all callers of the modified methods as well as the modified methods,
// and recalculate all results
CallGraph callGraph = wcetTool.getCallGraph();
final Set<ExecutionContext> rootNodes = new LinkedHashSet<ExecutionContext>();
for (MethodInfo root : changedMethods) {
rootNodes.addAll(callGraph.getNodes(root));
}
// we also need to recalculate for new nodes.. we simply go down callstring-length from the changed methods
final int callstringLength = AppInfo.getSingleton().getCallstringLength();
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.isFirstVisit() && !wcaNodeFlow.containsKey(node)) {
rootNodes.add(node);
}
return depth <= callstringLength;
}
};
DFSTraverser<ExecutionContext,ContextEdge> traverser = new DFSTraverser<ExecutionContext, ContextEdge>(visitor);
traverser.traverse(callGraph.getGraph(), new ArrayList<ExecutionContext>(rootNodes));
// since we use the cache analysis for the WCA, we need to update all methods for which the
// classification changed too
for (MethodInfo method : analyses.getMethodCacheAnalysis().getClassificationChangeSet()) {
rootNodes.addAll(callGraph.getNodes(method));
}
Set<MethodInfo> changed = runAnalysis(wcetTool.getCallGraph().createInvokeGraph(rootNodes, true));
updateWCEP();
return changed;
}
///////////////////////////////////////////////////////////////////////////////
// Private methods
///////////////////////////////////////////////////////////////////////////////
private Set<MethodInfo> runAnalysis(DirectedGraph<ExecutionContext,ContextEdge> reversed) {
// Phew. The WCA only runs on acyclic callgraphs, we can therefore assume the
// reversed graph to be a DAG
TopologicalOrderIterator<ExecutionContext,ContextEdge> topOrder =
new TopologicalOrderIterator<ExecutionContext, ContextEdge>(reversed);
Set<MethodInfo> changed = new LinkedHashSet<MethodInfo>();
while (topOrder.hasNext()) {
ExecutionContext node = topOrder.next();
// At times like this I really wish Java would have type aliases ..
RecursiveWcetAnalysis<AnalysisContextLocal>.LocalWCETSolution sol =
recursiveAnalysis.computeSolution(node.getMethodInfo(),
new AnalysisContextLocal(cacheApproximation, node.getCallString()));
wcaNodeFlow.put(node, sol.getNodeFlowVirtual());
// TODO some logging would be nice, keep target-method WCET for comparison of speedup
if (node.getMethodInfo().equals(wcetTool.getTargetMethod())) {
lastWCET = sol.getCost().getCost();
logger.info("WCET: "+ lastWCET);
}
changed.add(node.getMethodInfo());
}
return changed;
}
private void setWCETOptions(MethodInfo targetMethod, boolean generateReports) {
Config config = wcetTool.getConfig();
config.setOption(ProjectConfig.TARGET_METHOD, targetMethod.getMemberID().toString());
config.setOption(ProjectConfig.DO_GENERATE_REPORTS, generateReports);
config.setOption(IPETConfig.DUMP_ILP, false);
config.getDebugGroup().setOption(ProjectConfig.DUMP_TARGET_CALLGRAPH, DUMPTYPE.off);
}
private void updateWCEP() {
if (!provideWCAExecCount) return;
execCounts.clear();
for (MethodInfo root : getWcaTargets()) {
execCounts.put(root, 1L);
}
NodeVisitor<ExecutionContext> visitor = new NodeVisitor<ExecutionContext>() {
@Override
public boolean visitNode(ExecutionContext context) {
MethodInfo method = context.getMethodInfo();
MethodCode code = method.getCode();
long ec = getExecCount(method);
// skip methods which are not on the WCET path.. we can ship iterating over the childs too..
if (ec == 0) return false;
// iterate over all blocks in the CFG, find all invokes and add block execution counts to invokees
ControlFlowGraph cfg = method.getCode().getControlFlowGraph(false);
for (CFGNode node : cfg.getGraph().vertexSet()) {
if (node instanceof InvokeNode) {
InvokeNode inv = (InvokeNode) node;
long ef = getExecFrequency(method, node);
for (MethodInfo invokee : inv.getImplementingMethods()) {
addExecCount(invokee, ec * ef);
}
} else if (node instanceof BasicBlockNode) {
// check if we have a JVM invoke here (or an invoke not in a dedicated node..)
for (InstructionHandle ih : node.getBasicBlock().getInstructions()) {
if (!code.isInvokeSite(ih)) continue;
long ef = getExecFrequency(method, node);
for (MethodInfo invokee : method.getAppInfo().findImplementations(code.getInvokeSite(ih))) {
addExecCount(invokee, ec * ef);
}
}
}
}
return true;
}
};
TopologicalTraverser<ExecutionContext,ContextEdge> topOrder =
new TopologicalTraverser<ExecutionContext, ContextEdge>(wcetTool.getCallGraph().getGraph(),visitor);
topOrder.traverse();
}
private void addExecCount(MethodInfo method, long ec) {
Long count = execCounts.get(method);
if (count == null) {
execCounts.put(method, ec);
} else {
execCounts.put(method, ec + count);
}
}
}