/*
* This file is part of JOP, the Java Optimized Processor
* see <http://www.jopdesign.com/>
*
* Copyright (C) 2010, 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.wcet;
import com.jopdesign.common.AppInfo;
import com.jopdesign.common.ClassInfo;
import com.jopdesign.common.EmptyAppEventHandler;
import com.jopdesign.common.KeyManager.CustomKey;
import com.jopdesign.common.KeyManager.KeyType;
import com.jopdesign.common.MethodCode;
import com.jopdesign.common.MethodInfo;
import com.jopdesign.common.code.BasicBlock;
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.ExecutionContext;
import com.jopdesign.common.code.LoopBound;
import com.jopdesign.common.code.SymbolicMarker;
import com.jopdesign.dfa.analyses.LoopBounds;
import com.jopdesign.wcet.annotations.BadAnnotationException;
import com.jopdesign.wcet.annotations.LoopBoundExpr;
import com.jopdesign.wcet.annotations.SourceAnnotationReader;
import com.jopdesign.wcet.annotations.SourceAnnotations;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.log4j.Logger;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
/**
* This class provides callback methods for AppInfo and performs the annotation loading.
* TODO either rename this class or move the annotation-loading code into a dedicated class
* (which must then also contain dfaLoopBound()..)
*
* @author Stefan Hepp (stefan@stefant.org)
*/
public class WCETEventHandler extends EmptyAppEventHandler {
public static final Logger logger = Logger.getLogger(WCETTool.LOG_WCET_PROJECT+".WCETEventHandler");
// Using default loop bound will emit critical warning, but useful to
// find all unbounded loop bounds
public static final Long DEFAULT_LOOP_BOUND = 1024L;
private CustomKey annotationKey;
private WCETTool project;
private SourceAnnotationReader annotationReader;
private Set<BasicBlock> printedLoopBoundInfoMessage = new HashSet<BasicBlock>();
private boolean ignoreMissingLoopBounds = false;
public WCETEventHandler(WCETTool wcetTool) {
this.project = wcetTool;
}
@Override
public void onRegisterEventHandler(AppInfo appInfo) {
// TODO attach annotations to CFG/blocks/instructions instead of classInfo
annotationKey = appInfo.getKeyManager().registerKey(KeyType.STRUCT, "SourceAnnotations");
annotationReader = new SourceAnnotationReader(project);
}
@Override
public void onCreateControlFlowGraph(ControlFlowGraph cfg) {
try {
loadLoopAnnotations(cfg);
} catch (BadAnnotationException e) {
// TODO should we throw an error here instead?
// Since later analyses will expect loopbounds, they will most likely fail in some obscure way..
logger.fatal("Failed to load loopbounds for method "+cfg.getMethodInfo()+": "+e.getMessage(), e);
}
}
public SourceAnnotations getAnnotations(ClassInfo cli) throws BadAnnotationException, IOException {
SourceAnnotations annots = (SourceAnnotations) cli.getCustomValue(annotationKey);
if(annots == null) {
annots = annotationReader.readAnnotations(cli);
cli.setCustomValue(annotationKey, annots);
}
return annots;
}
public void setIgnoreMissingLoopBounds(boolean ignoreMissingLoopBounds) {
this.ignoreMissingLoopBounds = ignoreMissingLoopBounds;
}
public void loadLoopAnnotations(ClassInfo classInfo) throws BadAnnotationException {
for (MethodInfo method : classInfo.getMethods()) {
if (!method.hasCode()) continue;
loadLoopAnnotations(method.getCode().getControlFlowGraph(false));
}
}
/**
* load annotations for the flow graph.
*
* @param cfg the control flow graph of the method
* @throws BadAnnotationException if an annotations is missing
*/
public void loadLoopAnnotations(ControlFlowGraph cfg) throws BadAnnotationException {
SourceAnnotations wcaMap;
MethodInfo method = cfg.getMethodInfo();
MethodCode code = method.getCode();
ExecutionContext eCtx = new ExecutionContext(cfg.getMethodInfo());
for (CFGNode n : cfg.getLoopColoring().getHeadOfLoops()) {
BasicBlockNode headOfLoop = (BasicBlockNode) n;
BasicBlock block = headOfLoop.getBasicBlock();
// check if loopbound has already been loaded
if (block.getLoopBound() != null) {
// TODO maybe check if we already loaded annotations for this methodInfo before
// or at least check if the source-annotation is tighter than what is currently set?
continue;
}
Set<LoopBound> bounds = new HashSet<LoopBound>(2);
InstructionHandle first = block.getFirstInstruction();
InstructionHandle last = first;
ClassInfo sourceInfo = method.getCode().getSourceClassInfo(block.getFirstInstruction());
for (InstructionHandle ih : block.getInstructions()) {
ClassInfo cls = method.getCode().getSourceClassInfo(ih);
boolean isLast = ih.equals(block.getLastInstruction());
if (!cls.equals(sourceInfo) || isLast) {
try {
wcaMap = getAnnotations(method.getCode().getSourceClassInfo(block.getFirstInstruction()));
} catch (IOException e) {
throw new BadAnnotationException("IO Error reading annotation: " + e.getMessage(), e);
}
if (isLast) {
last = ih;
}
// search for loop annotation in range
int sourceRangeStart = code.getLineNumber(first);
int sourceRangeStop = code.getLineNumber(last);
bounds.addAll(wcaMap.annotationsForLineRange(sourceRangeStart, sourceRangeStop + 1));
first = ih;
}
last = ih;
}
if (bounds.size() > 1) {
String reason = "Ambiguous Annotation [" + bounds + "]";
throw new BadAnnotationException(reason, code, block);
}
LoopBound loopAnnot = null;
if (bounds.size() == 1) {
loopAnnot = bounds.iterator().next();
}
// if we have loop bounds from DFA analysis, use them
loopAnnot = dfaLoopBound(block, eCtx, loopAnnot);
if (loopAnnot == null) {
// throw new BadAnnotationException("No loop bound annotation",
// block,sourceRangeStart,sourceRangeStop);
// Bit of a hack: if we load CFGs before the callgraph is constructed, this will log errors anyway
if (ignoreMissingLoopBounds) {
logger.trace("No loop bound annotation: " + method + ":" + n +
" " + getLineRangeText(code, block) +
".\nApproximating with " + DEFAULT_LOOP_BOUND + ", but result is not safe anymore.");
} else if (project.getCallGraph() != null && !project.getCallGraph().containsMethod(method)) {
logger.debug("No loop bound annotation for non-WCET method: " + method + ":" + n +
" " + getLineRangeText(code, block) +
".\nApproximating with " + DEFAULT_LOOP_BOUND);
} else {
logger.error("No loop bound annotation: " + method + ":" + n +
" " + getLineRangeText(code, block) +
".\nApproximating with " + DEFAULT_LOOP_BOUND + ", but result is not safe anymore.");
}
loopAnnot = LoopBound.defaultBound(DEFAULT_LOOP_BOUND);
}
block.setLoopBound(loopAnnot);
}
}
private String getLineRangeText(MethodCode code, BasicBlock block) {
return "[line " + code.getLineString(block.getFirstInstruction()) + "-" +
code.getLineString(block.getLastInstruction()) + "]";
}
/**
* Get a loop bound from the DFA for a certain loop and call string and
* merge it with the annotated value.
* @return The loop bound to be used for further computations
*/
public LoopBound dfaLoopBound(BasicBlock headOfLoopBlock, ExecutionContext eCtx, LoopBound annotatedBound) {
LoopBounds lbAnalysis = project.getDfaLoopBounds();
if(lbAnalysis == null) return annotatedBound;
MethodInfo methodInfo = headOfLoopBlock.getMethodInfo();
int dfaUpperBound;
// Insert a try-catch to deal with failures of the DFA analysis
// FIXME: Bad style
try {
dfaUpperBound = lbAnalysis.getBound(headOfLoopBlock.getLastInstruction(),eCtx.getCallString());
} catch(NullPointerException ex) {
logger.error("Failed to retrieve DFA loop bound values", ex);
dfaUpperBound = -1;
}
if(dfaUpperBound < 0) {
if(! printedLoopBoundInfoMessage.contains(headOfLoopBlock)) {
logger.info("No DFA bound for " + methodInfo+"/"+headOfLoopBlock+
". Using manual bound: "+annotatedBound);
printedLoopBoundInfoMessage.add(headOfLoopBlock);
}
return annotatedBound;
}
LoopBound loopBound;
if(annotatedBound == null) {
loopBound = LoopBound.boundedAbove(dfaUpperBound);
logger.debug("Only DFA bound for "+methodInfo+"headOfLoopBlock");
} else {
loopBound = annotatedBound.clone();
// More testing would be nice
loopBound.addBound(LoopBoundExpr.numUpperBound(dfaUpperBound), SymbolicMarker.LOOP_ENTRY);
long loopUb = annotatedBound.getSimpleLoopBound().upperBound(eCtx);
if(dfaUpperBound < loopUb) {
/* This isn't unusual (context dependent loop bounds) */
if (logger.isDebugEnabled()) {
logger.debug("DFA analysis reports a smaller upper bound :"+dfaUpperBound+ " < "+loopUb+
" for "+methodInfo+"/"+headOfLoopBlock);
}
} else if (dfaUpperBound > loopUb) {
/* In principle this is possible, but usually a bad sign */
logger.warn("DFA analysis reports a larger upper bound: "+dfaUpperBound+ " > "+loopUb+
" for "+methodInfo);
} else {
if (logger.isDebugEnabled()) {
logger.debug("DFA and annotated loop bounds match for "+methodInfo);
}
}
}
if(! printedLoopBoundInfoMessage.contains(headOfLoopBlock)) {
logger.info("DFA bound for " + methodInfo+"/"+headOfLoopBlock+
": "+loopBound+". Manual bound info: "+annotatedBound);
printedLoopBoundInfoMessage.add(headOfLoopBlock);
}
return loopBound;
}
}