/*
* 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.greedy;
import com.jopdesign.common.AppInfo;
import com.jopdesign.common.MethodInfo;
import com.jopdesign.common.code.CallGraph.InvokeEdge;
import com.jopdesign.common.code.CallGraph.MethodNode;
import com.jopdesign.common.misc.AppInfoError;
import com.jopdesign.jcopter.JCopter;
import com.jopdesign.jcopter.analysis.AnalysisManager;
import com.jopdesign.jcopter.analysis.ExecFrequencyProvider;
import com.jopdesign.jcopter.analysis.LocalExecFrequencyProvider;
import com.jopdesign.jcopter.analysis.StacksizeAnalysis;
import com.jopdesign.jcopter.greedy.GreedyConfig.GreedyOrder;
import org.apache.log4j.Logger;
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.List;
import java.util.Map;
import java.util.Set;
/**
* This is the main optimizer, which uses CodeOptimizers to generate candidates and uses a greedy
* selection algorithm to select the candidates to optimize.
*
* TODO can we reuse Candidate and CodeOptimizer for different optimization strategies as well? Either move them
* to a different package or rename them to something more specific to avoid naming conflicts.
*
* @author Stefan Hepp (stefan@stefant.org)
*/
public class GreedyOptimizer {
private class MethodData {
private int maxLocals;
private MethodData(int maxLocals) {
this.maxLocals = maxLocals;
}
public int getMaxLocals() {
return maxLocals;
}
public void setMaxLocals(int maxLocals) {
this.maxLocals = maxLocals;
}
}
private final AppInfo appInfo;
private final JCopter jcopter;
private final GreedyConfig config;
private final List<CodeOptimizer> optimizers;
private int countCandidates;
private int countOptimized;
private int maxSteps;
private static final Logger logger = Logger.getLogger(JCopter.LOG_OPTIMIZER+".GreedyOptimizer");
public GreedyOptimizer(GreedyConfig config) {
this.jcopter = config.getJCopter();
this.config = config;
this.appInfo = config.getAppInfo();
this.optimizers = new ArrayList<CodeOptimizer>();
}
public void addOptimizer(CodeOptimizer optimizer) {
this.optimizers.add(optimizer);
}
public void optimize() {
List<MethodInfo> rootMethods = config.getTargetMethods();
// initialization
resetCounters();
boolean useWCAProvider = config.useWCA() && config.useWCAExecCount();
GreedyOrder order = config.getOrder();
if (order != GreedyOrder.WCAFirst) {
// TODO for now we require that for this to work the target must be the WCA root
// for this to work the WCAInvoker would need to use the ExecFrequencyAnalysis to
// multiply its results with the number of executions of the target-method for all
// WCA methods and use the ExecFrequencyAnalysis for everything else. Changesets must be
// updated as well.
if (!config.getTargetMethodSet().equals(config.getWCATargetSet())) {
logger.warn("Using the WCA for exec frequencies is currently only supported if order is WCAFirst "+
"or if the target method is the WCA target method");
useWCAProvider = false;
}
}
AnalysisManager analyses = initializeAnalyses(config.useWCEP() || useWCAProvider);
for (CodeOptimizer opt : optimizers) {
opt.initialize(analyses, rootMethods);
}
CandidateSelector selector;
if (config.useWCA()) {
GainCalculator gc = new GainCalculator(analyses);
if (config.useWCEP()) {
logger.info("Using WCEP driven selector");
selector = new WCEPRebateSelector(analyses, gc, config.getMaxCodesize());
} else {
logger.info("Using WCA driven selector");
selector = new WCETRebateSelector(analyses, gc, config.getMaxCodesize());
}
} else {
logger.info("WCA is disabled, not using WCA for optimization.");
selector = new ACETRebateSelector(analyses, new GainCalculator(analyses), config.getMaxCodesize());
}
selector.initialize(config, true);
ExecFrequencyProvider ecp = useWCAProvider ? analyses.getWCAInvoker() : analyses.getExecFrequencyAnalysis();
if (config.useLocalExecCount()) {
ecp = new LocalExecFrequencyProvider(ecp);
}
// dump initial callgraph
logger.info("Initial number of methods in target callgraph: "+
analyses.getTargetCallGraph().getMethodInfos().size());
analyses.getTargetCallGraph().dumpCallgraph(jcopter.getJConfig().getConfig(),
"greedy-target", config.getTargetCallgraphDumpType(), true);
// iterate over regions in callgraph
if (order == GreedyOrder.Global || (order == GreedyOrder.WCAFirst && !config.useWCA())) {
optimizeMethods(analyses, ecp, selector,
analyses.getTargetCallGraph().getMethodInfos());
} else if (order == GreedyOrder.Targets) {
for (MethodInfo target : config.getTargetMethods()) {
optimizeMethods(analyses, ecp, selector,
analyses.getTargetCallGraph().getReachableImplementationsSet(target));
}
} else if (order == GreedyOrder.WCAFirst) {
logger.info("Optimizing WCA target");
Set<MethodInfo> wcaMethods = analyses.getWCAMethods();
optimizeMethods(analyses, ecp, selector, wcaMethods);
selector.printStatistics();
// We do not want to include the wca methods in the second pass because inlining there could have negative
// effects on the WCET path due to the cache
Set<MethodInfo> others = new LinkedHashSet<MethodInfo>(analyses.getTargetCallGraph().getMethodInfos());
others.removeAll(wcaMethods);
logger.info("Optimizing non-WCA code");
//analyses.dumpTargetCallgraph("acet", true);
selector = new ACETRebateSelector(analyses, new GainCalculator(analyses), config.getMaxCodesize());
selector.initialize(config, false);
ecp = analyses.getExecFrequencyAnalysis();
if (config.useLocalExecCount()) {
ecp = new LocalExecFrequencyProvider(ecp);
}
optimizeMethods(analyses, ecp, selector, others);
} else if (order == GreedyOrder.TopDown || order == GreedyOrder.BottomUp) {
if (config.useWCA() && !analyses.hasWCATargetsOnly()) {
// TODO iterate over WCA and then non-wca graph or something in this case..
throw new AppInfoError("Order "+order+" currently only works with WCA if the target method is the WCA target");
}
TopologicalOrderIterator<MethodNode,InvokeEdge> topOrder =
new TopologicalOrderIterator<MethodNode, InvokeEdge>(
analyses.getTargetCallGraph().getAcyclicMergedGraph(order == GreedyOrder.BottomUp)
);
while (topOrder.hasNext()) {
MethodNode node = topOrder.next();
optimizeMethods(analyses, ecp, selector, Collections.singleton(node.getMethodInfo()));
}
} else {
throw new AppInfoError("Order "+order+" not yet implemented.");
}
// dump final callgraph
analyses.getTargetCallGraph().dumpCallgraph(jcopter.getJConfig().getConfig(),
"greedy-target-opt", config.getTargetCallgraphDumpType(), true);
selector.printStatistics();
printStatistics();
}
private void resetCounters() {
countCandidates = 0;
countOptimized = 0;
maxSteps = config.getMaxSteps();
}
private void printStatistics() {
for (CodeOptimizer o : optimizers) {
o.printStatistics();
}
logger.info("Candidates: "+countCandidates+", Optimized: "+countOptimized);
}
private AnalysisManager initializeAnalyses(boolean updateWCEP) {
AnalysisManager analyses = new AnalysisManager(jcopter);
analyses.initAnalyses(config.getTargetMethodSet(), config.getCacheAnalysisType(),
config.getCacheApproximation(), config.useMethodCacheStrategy(),
config.useWCA() ? config.getWCATargetSet() : null, updateWCEP);
logger.info("Callgraph nodes: "+analyses.getTargetCallGraph().getNodes().size());
return analyses;
}
private void optimizeMethods(AnalysisManager analyses, ExecFrequencyProvider ecp,
CandidateSelector selector, Set<MethodInfo> methods)
{
Map<MethodInfo,MethodData> methodData = new LinkedHashMap<MethodInfo, MethodData>(methods.size());
selector.clear();
if (maxSteps > 0 && countOptimized >= maxSteps) {
return;
}
// first find and initialize all candidates
for (MethodInfo method : methods) {
if (method.isNative()) continue;
// to update maxLocals
method.getCode().compile();
StacksizeAnalysis stacksize = analyses.getStacksizeAnalysis(method);
int locals = method.getCode().getMaxLocals();
for (CodeOptimizer optimizer : optimizers) {
Collection<Candidate> found;
found = optimizer.findCandidates(method, analyses, stacksize, locals);
selector.addCandidates(method, found);
countCandidates += found .size();
}
methodData.put(method, new MethodData(locals));
}
// now use the RebateSelector to order the candidates
selector.sortCandidates(ecp);
Set<MethodInfo> optimizedMethods = new LinkedHashSet<MethodInfo>();
Set<MethodInfo> candidateChanges = new LinkedHashSet<MethodInfo>();
Collection<Candidate> candidates = selector.selectNextCandidates(ecp);
while (candidates != null) {
optimizedMethods.clear();
candidateChanges.clear();
analyses.clearChangeSets();
// perform optimization
for (Candidate c : candidates) {
MethodInfo method = c.getMethod();
StacksizeAnalysis stacksize = analyses.getStacksizeAnalysis(method);
logger.info("Optimizing "+c.toString());
if (!c.optimize(analyses, stacksize)) continue;
countOptimized++;
if (maxSteps > 0 && countOptimized >= maxSteps) {
return;
}
// to update maxStack and positions
method.getCode().compile();
// Now we need to update the stackAnalysis and find new candidates in the optimized code
List<Candidate> newCandidates = new ArrayList<Candidate>();
if (c.getStart() != null) {
stacksize.analyze(c.getStart(), c.getEnd());
int locals = c.getMaxLocalsInRegion();
// find new candidates in optimized code
for (CodeOptimizer optimizer : optimizers) {
Collection<Candidate> found;
found = optimizer.findCandidates(method, analyses, stacksize, locals, c.getStart(), c.getEnd());
newCandidates.addAll(found);
}
countCandidates += newCandidates.size();
}
// Notify selector to update codesize, remove unreachable methods and to replace
// old candidates with new ones
selector.onSuccessfulOptimize(c, newCandidates);
optimizedMethods.add(method);
}
// Now we need to find out for which methods we need to recalculate the candidates..
// First we add all optimized methods since we added new candidates and changed the codesize
candidateChanges.addAll(optimizedMethods);
// small shortcut if we optimize one method at a time. In this case we only have one method to update anyway
if (methods.size() > 1) {
// For all direct callers the cache-miss-*costs* of invokes changed, so we add them too
// Actually we would only need to update caller candidates whose range includes the affected
// invokeSites, but well..
for (MethodInfo method : optimizedMethods) {
candidateChanges.addAll(appInfo.getCallGraph().getDirectInvokers(method));
}
// No need to add exec count changes, since candidates calculate values only per single execution,
// or is there? (cache miss count changes due to exec frequency changes are handled by the cache analysis)
// We need to find out for which invokeSites the cache-miss-*counts* (as used by
// Candidate#getDeltaCacheMissCosts()) of invoke and return changed, add to the changeset
candidateChanges.addAll(analyses.getMethodCacheAnalysis().getClassificationChangeSet());
}
// Now let the selector update its analyses and find out which additional methods need sorting
Set<MethodInfo> changeSet = selector.updateChangeSet(ecp, optimizedMethods, candidateChanges);
// for those methods with invokesites with cache-cost changes, recalculate the candidate-gains
// (assuming that the candidates do not use the WCA results, else we would recalculate in changeSet too)
// but only for methods which we optimize.
for (MethodInfo method : candidateChanges) {
// skip methods in changeset which are not being optimized
if (!methodData.containsKey(method)) continue;
selector.updateCandidates(method, ecp, analyses.getStacksizeAnalysis(method));
}
// Finally use the set of methods for which something changed, and re-sort all candidates of those methods
if (methods.size() == 1) {
selector.sortCandidates(ecp, methods);
} else {
if (logger.isTraceEnabled()) {
logger.trace("Sort changes "+changeSet.size());
}
selector.sortCandidates(ecp, changeSet);
}
// Finally, select the next candidates
candidates = selector.selectNextCandidates(ecp);
}
}
}