/*
* 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.ClassInfo;
import com.jopdesign.common.MethodInfo;
import com.jopdesign.common.code.CallGraph.ContextEdge;
import com.jopdesign.common.code.ExecutionContext;
import com.jopdesign.common.graphutils.DFSTraverser;
import com.jopdesign.common.graphutils.DFSTraverser.DFSVisitor;
import com.jopdesign.common.graphutils.DFSTraverser.EmptyDFSVisitor;
import com.jopdesign.common.misc.AppInfoError;
import com.jopdesign.common.processormodel.ProcessorModel;
import com.jopdesign.jcopter.JCopter;
import com.jopdesign.jcopter.analysis.AnalysisManager;
import com.jopdesign.jcopter.analysis.ExecFrequencyProvider;
import com.jopdesign.jcopter.analysis.StacksizeAnalysis;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.log4j.Logger;
import org.jgrapht.DirectedGraph;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author Stefan Hepp (stefan@stefant.org)
*/
public abstract class RebateSelector implements CandidateSelector {
public class RebateRatio implements Comparable<RebateRatio> {
private final Candidate candidate;
private final long gain;
private final float ratio;
private final int order;
protected RebateRatio(Candidate candidate, long gain, float ratio) {
this.candidate = candidate;
this.gain = gain;
this.ratio = ratio;
this.order = depthMap.get(candidate.getMethod());
}
public Candidate getCandidate() {
return candidate;
}
public long getGain() {
return gain;
}
public float getRatio() {
return ratio;
}
public int getOrder() {
return order;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RebateRatio that = (RebateRatio) o;
return Float.compare(that.getRatio(), ratio) == 0 && candidate.equals(that.getCandidate());
}
@Override
public int hashCode() {
int result = candidate.hashCode();
result = 31 * result + (ratio != +0.0f ? Float.floatToIntBits(ratio) : 0);
return result;
}
@Override
public int compareTo(RebateRatio o) {
if (ratio == o.getRatio()) {
// since we want to keep different entries with the same ratio, use candidate as tiebreaker
if (candidate.equals(o.getCandidate())) return 0;
if (order == o.getOrder()) {
// same method, use candidate as tie-breaker
return candidate.hashCode() < o.getCandidate().hashCode() ? -1 : 1;
}
return order > o.getOrder() ? -1 : 1;
}
return ratio < o.getRatio() ? -1 : 1;
}
public Collection<Candidate> getCandidates() {
return Collections.singleton(candidate);
}
}
protected class MethodData {
private List<Candidate> candidates;
private List<RebateRatio> ratios;
private MethodData() {
candidates = new ArrayList<Candidate>();
ratios = new ArrayList<RebateRatio>();
}
public void addCandidates(Collection<Candidate> c) {
for (Candidate candidate : c) {
if (checkConstraints(candidate)) {
candidates.add(candidate);
}
}
}
public List<Candidate> getCandidates() {
return candidates;
}
public List<RebateRatio> getRatios() {
return ratios;
}
}
private static final Logger logger = Logger.getLogger(JCopter.LOG_OPTIMIZER+".RebateSelector");
protected final AnalysisManager analyses;
protected final ProcessorModel processorModel;
protected final Map<MethodInfo, MethodData> methodData;
// The death-map..
private final Map<MethodInfo,Integer> depthMap;
private boolean usesCodeRemover;
private int maxGlobalSize;
private int globalCodesize;
private PrintWriter dump = null;
public RebateSelector(AnalysisManager analyses, int maxGlobalSize) {
this.analyses = analyses;
this.maxGlobalSize = maxGlobalSize;
this.processorModel = AppInfo.getSingleton().getProcessorModel();
usesCodeRemover = analyses.getJCopter().getExecutor().useCodeRemover();
methodData = new LinkedHashMap<MethodInfo, MethodData>();
depthMap = new LinkedHashMap<MethodInfo, Integer>();
}
@Override
public void initialize(GreedyConfig config, boolean dumpStats) {
// calculate current global codesize
globalCodesize = 0;
if (usesCodeRemover) {
for (MethodInfo method : AppInfo.getSingleton().getCallGraph().getMethodInfos()) {
if (!method.hasCode()) continue;
globalCodesize += method.getCode().getNumberOfBytes();
}
} else {
for (ClassInfo cls : AppInfo.getSingleton().getClassInfos()) {
for (MethodInfo method : cls.getMethods()) {
if (!method.hasCode()) continue;
globalCodesize += method.getCode().getNumberOfBytes();
}
}
}
// we need a tie-breaker for the candidate selection, and to make the results deterministic
// so we use a topological order of the (initial) callgraph
DFSVisitor<ExecutionContext,ContextEdge> visitor = new EmptyDFSVisitor<ExecutionContext, ContextEdge>() {
private int counter = 1;
@Override
public void postorder(ExecutionContext node) {
depthMap.put(node.getMethodInfo(), counter++);
}
};
DirectedGraph<ExecutionContext,ContextEdge> graph = analyses.getTargetCallGraph().getReversedGraph();
DFSTraverser<ExecutionContext,ContextEdge> traverser = new DFSTraverser<ExecutionContext, ContextEdge>(visitor);
traverser.traverse(graph);
logger.info("Initial codesize: " + globalCodesize + " bytes");
if (config.doDumpStats() && dumpStats) {
try {
File statsFile = config.getStatsFile();
dump = new PrintWriter(statsFile);
dump.print("total codesize, ");
if (analyses.useWCAInvoker()) dump.print("WCET, ");
dump.println("candidate, ratio, gain, local gain, cache, local cache, delta codesize, frequency");
} catch (FileNotFoundException e) {
throw new AppInfoError("Could not initialize dump file", e);
}
}
}
@Override
public void clear() {
methodData.clear();
}
@Override
public void printStatistics() {
logger.info("Codesize after optimization: " + globalCodesize + " bytes");
dumpStats();
if (dump != null) {
dump.close();
}
}
private void dumpStats() {
if (dump == null) return;
dump.print(globalCodesize+", ");
if (analyses.useWCAInvoker()) {
dump.print(analyses.getWCAInvoker().getLastWCET());
}
}
public void addCandidates(MethodInfo method, Collection<Candidate> candidates) {
MethodData data = methodData.get(method);
if (data == null) {
data = new MethodData();
methodData.put(method, data);
}
data.addCandidates(candidates);
}
public void removeCandidate(Candidate candidate) {
MethodData data = methodData.get(candidate.getMethod());
data.getCandidates().remove(candidate);
}
@Override
public void removeCandidates(MethodInfo method) {
MethodData data = methodData.remove(method);
if (data != null) {
onRemoveMethodData(data);
}
}
@Override
public void removeCandidates(MethodInfo method, InstructionHandle start, InstructionHandle end) {
// TODO go through all candidates of the method, remove all with overlapping range (use positions to check)
// for now, we just assume that candidates do not overlap ..
MethodData data = methodData.get(method);
}
@Override
public Collection<Candidate> getCandidates(MethodInfo method) {
return methodData.get(method).getCandidates();
}
@Override
public void onSuccessfulOptimize(Candidate optimized, List<Candidate> newCandidates) {
globalCodesize += getDeltaGlobalCodesize(optimized);
// We can remove candidates from methods which are no longer reachable here already
// This does not find everything, candidates which are only unreachable in the target graph are removed later
Collection<MethodInfo> unreachable = optimized.getUnreachableMethods();
if (unreachable != null && !unreachable.isEmpty()) {
for (MethodInfo m : unreachable) {
// codesize of removed candidates already handled above
removeCandidates(m);
}
}
// replace old candidates with new ones in range
removeCandidate(optimized);
removeCandidates(optimized.getMethod(), optimized.getStart(), optimized.getEnd());
addCandidates(optimized.getMethod(), newCandidates);
}
@Override
public void updateCandidates(MethodInfo method, ExecFrequencyProvider ecp, StacksizeAnalysis stacksizeAnalysis) {
MethodData data = methodData.get(method);
if (data == null) return;
Iterator<Candidate> it = data.getCandidates().iterator();
while (it.hasNext()) {
Candidate c = it.next();
if (!c.recalculate(analyses, stacksizeAnalysis)) {
it.remove();
}
/* we check this in selectCandidate anyway..
if (!checkConstraints(c)) {
it.remove();
}
*/
}
}
@Override
public void sortCandidates(ExecFrequencyProvider ecp, Set<MethodInfo> changedMethods) {
for (MethodInfo method : changedMethods) {
MethodData data = methodData.get(method);
// changed methods which are not optimized or not reachable anymore are skipped
if (data == null) continue;
// remove candidates of methods which are no longer reachable
if (!analyses.getTargetCallGraph().containsMethod(method)) {
removeCandidates(method);
continue;
}
sortMethodData(ecp, data);
}
}
protected boolean checkConstraints(Candidate candidate) {
// check local and global codesize
int size = candidate.getMethod().getCode().getNumberOfBytes();
size += candidate.getDeltaLocalCodesize();
if (size > processorModel.getMaxMethodSize()) return false;
if (maxGlobalSize > 0) {
int newGlobalSize = globalCodesize + getDeltaGlobalCodesize(candidate);
if (newGlobalSize > maxGlobalSize) return false;
}
return true;
}
protected RebateRatio createRatio(GainCalculator gc, ExecFrequencyProvider ecp, Candidate candidate, long gain) {
float codesize = getDeltaGlobalCodesize(candidate);
float ratio;
if (codesize > 0) {
ratio = gc.improveGain(ecp, candidate, gain) / codesize;
} else {
// little hack: if we have no codesize increase, use just the gain as factor
ratio = gc.improveGain(ecp, candidate, gain);
}
return new RebateRatio(candidate, gain, ratio);
}
protected void logSelection(ExecFrequencyProvider ecp, RebateRatio ratio) {
if (ratio == null) return;
long localGain = ratio.getCandidate().getLocalGain();
long cache = analyses.getMethodCacheAnalysis().getDeltaCacheMissCosts(ecp, ratio.getCandidate());
long localCache = ratio.getCandidate().getDeltaCacheMissCosts(analyses, ecp);
int codesize = ratio.getCandidate().getDeltaLocalCodesize();
long execCount = ecp.getExecCount(ratio.getCandidate().getMethod(), ratio.getCandidate().getEntry());
logger.info("Selected ratio " + ratio.getRatio() + ", gain " + ratio.getGain() +
" local " + localGain + " cache " + cache + " cache local " + localCache +
" codesize " + codesize + " freq " + execCount);
// logging stats from last iteration/initial stats
dumpStats();
// logging selection
if (dump != null) {
dump.print(", "+ratio.getCandidate().toString());
dump.print(", "+ratio.getRatio()+", "+ratio.getGain()+", "+ localGain);
dump.print(", "+cache +", "+localCache+", "+codesize+", "+execCount);
dump.println();
}
}
public int getDeltaGlobalCodesize(Candidate candidate) {
int size = candidate.getDeltaLocalCodesize();
if (usesCodeRemover) {
Collection<MethodInfo> removed = candidate.getUnreachableMethods();
if (removed != null) {
for (MethodInfo m : removed) {
size -= m.getCode().getNumberOfBytes();
}
}
}
return size;
}
protected abstract void onRemoveMethodData(MethodData data);
protected abstract void sortMethodData(ExecFrequencyProvider ecp, MethodData data);
}