/*
* This file is part of JOP, the Java Optimized Processor
* see <http://www.jopdesign.com/>
*
* Copyright (C) 2008, Benedikt Huber (benedikt.huber@gmail.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.common.code;
import com.jopdesign.common.AppInfo;
import com.jopdesign.common.ClassInfo;
import com.jopdesign.common.MethodCode;
import com.jopdesign.common.MethodInfo;
import com.jopdesign.common.logger.LogConfig;
import com.jopdesign.common.processormodel.ProcessorModel;
import org.apache.bcel.Constants;
import org.apache.bcel.generic.ATHROW;
import org.apache.bcel.generic.BranchInstruction;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.EmptyVisitor;
import org.apache.bcel.generic.GotoInstruction;
import org.apache.bcel.generic.IfInstruction;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.JsrInstruction;
import org.apache.bcel.generic.MONITORENTER;
import org.apache.bcel.generic.PUTFIELD;
import org.apache.bcel.generic.PUTSTATIC;
import org.apache.bcel.generic.ReturnInstruction;
import org.apache.bcel.generic.Select;
import org.apache.bcel.generic.StoreInstruction;
import org.apache.bcel.generic.UnconditionalBranch;
import org.apache.log4j.Logger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
/**
* A lightweight basic block class, based on linked lists.<br/>
* <p/>
* See: A more elaborated attempt to add BasicBlocks to BCEL
* http://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/ba/BasicBlock.html
*
* @author Benedikt Huber (benedikt.huber@gmail.com)
* @author Stefan Hepp (stefan@stefant.org)
*/
public class BasicBlock {
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(LogConfig.LOG_CFG + ".BasicBlock");
/**
* Flow annotations for instructions.
* Should only be used by {@link ControlFlowGraph}
*/
static class FlowInfo {
private boolean alwaysTaken = false;
private boolean splitBefore = false;
private boolean splitAfter = false;
private boolean exit = false;
private final List<FlowTarget> targets = new ArrayList<FlowTarget>();
void addTarget(InstructionHandle ih, ControlFlowGraph.EdgeKind kind) {
targets.add(new FlowTarget(ih, kind));
}
public boolean isAlwaysTaken() {
return alwaysTaken;
}
public boolean doSplitBefore() {
return splitBefore;
}
public boolean doSplitAfter() {
return splitAfter;
}
public boolean isExit() {
return exit;
}
public List<FlowTarget> getTargets() {
return targets;
}
}
/**
* Represents targets of a control flow instruction
*/
static class FlowTarget {
private InstructionHandle target;
private ControlFlowGraph.EdgeKind edgeKind;
FlowTarget(InstructionHandle target, ControlFlowGraph.EdgeKind edgeKind) {
this.target = target;
this.edgeKind = edgeKind;
}
public InstructionHandle getTarget() {
return target;
}
public ControlFlowGraph.EdgeKind getEdgeKind() {
return edgeKind;
}
public void setTarget(InstructionHandle target) {
this.target = target;
}
public void setEdgeKind(ControlFlowGraph.EdgeKind edgeKind) {
this.edgeKind = edgeKind;
}
@Override
public String toString() {
return "FlowTarget<" + target.getPosition() + "," + edgeKind + ">";
}
}
/**
* Keys for the custom {@link InstructionHandle} attributes
*/
private enum InstrField { FLOW_INFO }
/**
* @param ih the instruction handle to check
* @return the flowInfo associated with an {@link InstructionHandle}
*/
private static FlowInfo getFlowInfo(InstructionHandle ih) {
return (FlowInfo) ih.getAttribute(InstrField.FLOW_INFO);
}
private final LinkedList<InstructionHandle> instructions = new LinkedList<InstructionHandle>();
private final MethodCode methodCode;
private FlowInfo exitFlowInfo;
/**
* Create a basic block
*
* @param methodCode The method code this basic block belongs to
*/
public BasicBlock(MethodCode methodCode) {
this.methodCode = methodCode;
}
public AppInfo getAppInfo() {
return methodCode.getAppInfo();
}
public ClassInfo getClassInfo() {
return methodCode.getClassInfo();
}
public MethodInfo getMethodInfo() {
return methodCode.getMethodInfo();
}
/**
* Get the constant pool associated with the method of this basic block
*
* @return the ConstantPoolGen of the ClassInfo
*/
public ConstantPoolGen cpg() {
return this.getMethodInfo().getClassInfo().getConstantPoolGen();
}
public FlowInfo getExitFlowInfo() {
return exitFlowInfo;
}
private void setExitFlowInfo(FlowInfo exitFlowInfo) {
// used only by the builder?
this.exitFlowInfo = exitFlowInfo;
}
public void setLoopBound(LoopBound loopBound) {
methodCode.setLoopBound(getLastInstruction(), loopBound);
}
public LoopBound getLoopBound() {
// TODO we might need to handle block copy/split/.. to keep this value attached to the correct handle
// we can only store and retrieve loopbounds here, but not call the DFA tool.
// DFA should set its loopbounds after it finished its analysis.
// Currently, the DFA loopbounds are set to the CFG by the WCETEventHandler on creation of the CFG
return methodCode.getLoopBound(getLastInstruction());
}
/**
* add an instruction to this basic block
* @param ih the instruction to add
*/
public void addInstruction(InstructionHandle ih) {
this.instructions.add(ih);
}
/**
* Get the first instruction of an basic block
* (potential target of control flow instruction)
*
* @return the first instruction in this block.
*/
public InstructionHandle getFirstInstruction() {
return this.instructions.getFirst();
}
/**
* Get the last instruction of an basic block
* (potential control flow instruction)
*
* @return the last instruction of this block
*/
public InstructionHandle getLastInstruction() {
return this.instructions.getLast();
}
/**
* Get the invoke instruction of the basic block (which must be
* the only instruction in the basic block)
*
* @return the invoke instruction, or <code>null</code>, if the basic block doesn't
* contain an invoke instruction or if it is a special invoke.
* @throws ControlFlowGraph.ControlFlowError
* if there is more than one invoke instruction in the block.
* @see ProcessorModel#isSpecialInvoke(MethodInfo, Instruction)
*/
public InstructionHandle getTheInvokeInstruction() {
InstructionHandle theInvInstr = null;
for (InstructionHandle ih : this.instructions) {
if (!(ih.getInstruction() instanceof InvokeInstruction)) continue;
InvokeInstruction inv = (InvokeInstruction) ih.getInstruction();
if (this.getAppInfo().getProcessorModel().isSpecialInvoke(methodCode.getMethodInfo(), inv)) {
continue;
}
if (theInvInstr != null) {
throw new ControlFlowGraph.ControlFlowError("More than one invoke instruction in a basic block");
}
theInvInstr = ih;
}
return theInvInstr;
}
/**
* @return the BranchInstruction of the basic block, or {@code null} if there is none.
*/
public BranchInstruction getBranchInstruction() {
Instruction last = this.getLastInstruction().getInstruction();
return ((last instanceof BranchInstruction) ? ((BranchInstruction) last) : null);
}
/**
* @return the list of {@link InstructionHandle}s, which make up this basic block
*/
public List<InstructionHandle> getInstructions() {
return this.instructions;
}
/**
* @return number of bytes in this basic block
*/
public int getNumberOfBytes() {
int len = 0;
for (InstructionHandle ih : this.instructions) {
len += getAppInfo().getProcessorModel().getNumberOfBytes(
methodCode.getMethodInfo(), ih.getInstruction()
);
}
return len;
}
/**
* @return all source code lines this basic block maps to
*/
public Map<ClassInfo,TreeSet<Integer>> getSourceLines() {
Map<ClassInfo,TreeSet<Integer>> map = new HashMap<ClassInfo, TreeSet<Integer>>(2);
for (InstructionHandle ih : instructions) {
ClassInfo cls = methodCode.getSourceClassInfo(ih);
TreeSet<Integer> lines = map.get(cls);
if (lines == null) {
lines = new TreeSet<Integer>();
map.put(cls,lines);
}
int line = methodCode.getLineNumber(ih);
if (line >= 0) lines.add(line);
}
return map;
}
/**
* @return a human readable string representation of the location of the first instruction
* in the basic block
*/
public String getStartLine() {
ClassInfo cls = null;
int line = -1;
for(InstructionHandle ih : instructions) {
cls = methodCode.getSourceClassInfo(ih);
line = methodCode.getLineNumber(this.getFirstInstruction());
if(line >= 0) break;
}
if(line >= 0) {
return cls.getSourceFileName()+":"+line;
} else {
return getMethodInfo().getClassInfo().getSourceFileName()+":"+getMethodInfo().getFQMethodName();
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BasicBlock that = (BasicBlock) o;
if (!instructions.equals(that.getInstructions())) return false;
if (!getMethodInfo().equals(that.getMethodInfo())) return false;
return true;
}
@Override
public int hashCode() {
int result = instructions.hashCode();
result = 31 * result + methodCode.hashCode();
return result;
}
@Override
public String toString() {
return "BasicBlock: " +
instructions;
}
/*---------------------------------------------------------------------------
* Control flow graph construction, compilation
*---------------------------------------------------------------------------
*/
/**
* Override this class to get specific basic block partitioning
*/
@SuppressWarnings({"AccessingNonPublicFieldOfAnotherObject"})
public static class InstructionTargetVisitor extends EmptyVisitor {
private FlowInfo flowInfo;
private HashSet<InstructionHandle> targeted;
private MethodCode methodCode;
protected InstructionTargetVisitor(MethodCode m) {
this.targeted = new HashSet<InstructionHandle>();
this.methodCode = m;
}
public boolean isTarget(InstructionHandle ih) {
return targeted.contains(ih);
}
public void visitInstruction(InstructionHandle ih) {
ih.accept(this);
if (methodCode.getAppInfo().getProcessorModel().isImplementedInJava(methodCode.getMethodInfo(), ih.getInstruction())) {
flowInfo.splitBefore = true;
flowInfo.splitAfter = true;
}
}
@Override
public void visitBranchInstruction(BranchInstruction obj) {
flowInfo.splitAfter = true; /* details follow in goto/if/jsr/select */
}
@Override
public void visitGotoInstruction(GotoInstruction obj) {
flowInfo.addTarget(obj.getTarget(), ControlFlowGraph.EdgeKind.GOTO_EDGE);
this.targeted.add(obj.getTarget());
flowInfo.alwaysTaken = true;
}
@Override
public void visitIfInstruction(IfInstruction obj) {
flowInfo.addTarget(obj.getTarget(), ControlFlowGraph.EdgeKind.BRANCH_EDGE);
this.targeted.add(obj.getTarget());
}
@Override
public void visitSelect(Select obj) {
super.visitSelect(obj);
flowInfo.addTarget(obj.getTarget(), ControlFlowGraph.EdgeKind.SELECT_EDGE);
this.targeted.add(obj.getTarget());
// Note that getTargets() does not include the default target
for (InstructionHandle tih : obj.getTargets()) {
flowInfo.addTarget(tih, ControlFlowGraph.EdgeKind.SELECT_EDGE);
this.targeted.add(tih);
}
}
@Override
public void visitJsrInstruction(JsrInstruction obj) {
flowInfo.addTarget(obj.getTarget(), ControlFlowGraph.EdgeKind.JSR_EDGE);
this.targeted.add(obj.getTarget());
flowInfo.alwaysTaken = true;
}
/**
* FIXME: Exceptions aren't supported yet, but to avoid
* early bail out, we assume exceptions terminate execution (for now)
*/
@Override
public void visitATHROW(ATHROW obj) {
flowInfo.exit = true;
flowInfo.splitAfter = true;
}
// Not 100% necessary, but simplifies program analysis a lot
@Override
public void visitInvokeInstruction(InvokeInstruction obj) {
if (!methodCode.getAppInfo().getProcessorModel().isSpecialInvoke(methodCode.getMethodInfo(), obj)) {
flowInfo.splitBefore = true;
flowInfo.splitAfter = true;
}
}
// Not necessary, but nice for synchronized block analysis
@Override
public void visitMONITORENTER(MONITORENTER obj) {
flowInfo.splitBefore = true;
}
@Override
public void visitReturnInstruction(ReturnInstruction obj) {
flowInfo.splitAfter = true;
flowInfo.exit = true;
}
public FlowInfo getFlowInfo(InstructionHandle ih) {
flowInfo = new FlowInfo();
visitInstruction(ih);
return flowInfo;
}
}
/**
* Create a vector of basic blocks, annotated with flow information
*
* @param methodCode The MethodCode of the method from which basic blocks should be extracted.
* @return A vector of BasicBlocks, which instruction handles annotated with
* flow information.
*/
static List<BasicBlock> buildBasicBlocks(MethodCode methodCode) {
InstructionTargetVisitor itv = new InstructionTargetVisitor(methodCode);
List<BasicBlock> basicBlocks = new LinkedList<BasicBlock>();
// We do want to have the latest code, so we compile any existing, *attached* CFG first.
// However, we do NOT want to remove this CFG, and we do *NOT* want to trigger the onBeforeCodeModify event,
// else we might remove all CFGs for this method, which we want to avoid if we only modify the graph but do not
// compile it (the event will be triggered when CFG#compile() is called).
InstructionList il = methodCode.getInstructionList(true, false);
il.setPositions(true);
/* Step 1: compute flow info */
for (InstructionHandle ih : il.getInstructionHandles()) {
ih.addAttribute(InstrField.FLOW_INFO, itv.getFlowInfo(ih));
}
/* Step 2: create basic blocks */
{
BasicBlock bb = new BasicBlock(methodCode);
InstructionHandle[] handles = il.getInstructionHandles();
for (int i = 0; i < handles.length; i++) {
InstructionHandle ih = handles[i];
bb.addInstruction(ih);
boolean doSplit = getFlowInfo(ih).doSplitAfter();
if (i + 1 < handles.length) {
doSplit |= itv.isTarget(handles[i + 1]);
doSplit |= getFlowInfo(handles[i + 1]).doSplitBefore();
}
if (doSplit) {
bb.setExitFlowInfo(getFlowInfo(ih));
basicBlocks.add(bb);
bb = new BasicBlock(methodCode);
}
ih.removeAttribute(InstrField.FLOW_INFO);
}
if (!bb.getInstructions().isEmpty()) {
// be nice to DFA stuff, and ignore NOPs
for (int i = bb.getInstructions().size() - 1; i >= 0; --i) {
InstructionHandle x = bb.getInstructions().get(i);
if (x.getInstruction().getOpcode() != Constants.NOP) {
throw new AssertionError("[INTERNAL ERROR] Last instruction " + x +
" in code does not change control flow - this is impossible");
}
}
}
}
return basicBlocks;
}
/**
* Append the instructions of this block to an instruction list.
*
* @see MethodCode#copyCustomValues(MethodInfo, InstructionHandle, InstructionHandle)
* @param sourceInfo the method info containing the source instructions, used to copy custom values.
* @param il the instruction list to append to.
* @param attributes a list of attribute keys to copy in addition to those managed by MethodCode and BasicBlock.
*/
public void appendTo(MethodInfo sourceInfo, InstructionList il, Object[] attributes) {
List<InstructionHandle> old = new ArrayList<InstructionHandle>(instructions);
instructions.clear();
for (InstructionHandle ih : old) {
InstructionHandle newIh;
if (ih.getInstruction() instanceof BranchInstruction) {
newIh = il.append((BranchInstruction)ih.getInstruction());
} else {
newIh = il.append(ih.getInstruction());
}
// link to new handles, find first and last handle
instructions.add(newIh);
// we need to copy all attributes. FlowInfo should not be needed.
methodCode.copyCustomValues(sourceInfo, newIh, ih);
for (Object key : attributes) {
Object value = ih.getAttribute(key);
if (value != null) newIh.addAttribute(key, value);
}
methodCode.retarget(ih, newIh);
}
}
/*---------------------------------------------------------------------------
* Dump BasicBlock
*---------------------------------------------------------------------------
*/
/**
* <p>Compact, human-readable String representation of the basic block.</p>
* <p/>
* <p>Mixed Stack notation, with at most one side-effect statement per line.</p>
* Example:<br/>
* {@code local_0 <- sipush[3] sipush[4] dup add add} <br/>
* {@code local_1 <- load[local_0] load[local_0] mul}
*
* @return a compact string representation of this block
*/
public String dump() {
StringBuilder sb = new StringBuilder();
Iterator<InstructionHandle> ihIter = this.instructions.iterator();
InstructionPrettyPrinter ipp = new InstructionPrettyPrinter();
while (ihIter.hasNext()) {
InstructionHandle ih = ihIter.next();
ipp.visitInstruction(ih);
}
sb.append(ipp.getBuffer());
return sb.toString();
}
/* Prototyping */
/* TODO: Refactor into some pretty printing class */
private class InstructionPrettyPrinter extends EmptyVisitor {
private StringBuilder sb;
private StringBuilder lineBuffer;
private boolean visited;
private int startPos = -1, lastPos = -1;
private int currentPos;
private Integer address;
public InstructionPrettyPrinter() {
this.sb = new StringBuilder();
this.lineBuffer = new StringBuilder();
}
public StringBuilder getBuffer() {
nextLine();
return sb;
}
public void visitInstruction(InstructionHandle ih) {
this.visited = false;
//this.address = cfg.getConstAddress(ih);
currentPos = methodCode.getLineNumber(ih);
ih.accept(this);
if (!visited) {
String s = ih.getInstruction().toString(cpg().getConstantPool());
append(s);
}
}
private void nextLine() {
if (lineBuffer.length() > 0) {
String start = startPos < 0 ? "?" : ("" + startPos);
String end = lastPos < 0 ? "?" : ("" + lastPos);
if (startPos != lastPos) lineBuffer.insert(0, "[" + start + "-" + end + "] ");
else lineBuffer.insert(0, "[" + start + "] ");
sb.append(lineBuffer);
sb.append("\n");
lineBuffer = new StringBuilder();
startPos = currentPos;
lastPos = currentPos;
}
}
@SuppressWarnings({"AssignmentToMethodParameter"})
private void append(String stackOp) {
if (lineBuffer.length() > 0) {
if (lineBuffer.length() < 45) lineBuffer.append(" ");
else lineBuffer.append("\n \\ ");
}
if (address != null) stackOp = String.format("%s<%d>", stackOp, address);
lineBuffer.append(stackOp);
markVisited();
}
@SuppressWarnings({"AssignmentToMethodParameter"})
private void assign(String lhs) {
if (address != null) lhs = String.format("%s<%d>", lhs, address);
lineBuffer.insert(0, lhs + "<-");
nextLine();
markVisited();
visited = true;
}
private void markVisited() {
visited = true;
if (startPos < 0) startPos = currentPos;
lastPos = currentPos;
}
@Override
public void visitBranchInstruction(BranchInstruction obj) {
nextLine();
}
@Override
public void visitUnconditionalBranch(UnconditionalBranch obj) {
nextLine();
}
@Override
public void visitStoreInstruction(StoreInstruction obj) {
assign("$" + obj.getIndex());
}
@Override
public void visitPUTFIELD(PUTFIELD obj) {
assign(obj.getFieldName(cpg()));
}
@Override
public void visitPUTSTATIC(PUTSTATIC obj) {
String fieldName = obj.getFieldName(cpg());
assign(fieldName);
}
@Override
public void visitInvokeInstruction(InvokeInstruction obj) {
append(obj.toString());
}
}
}