package org.dynjs.ir.representations;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Iterator;
import java.util.List;
import org.dynjs.ir.Instruction;
import org.dynjs.ir.instructions.Branch;
import org.dynjs.ir.instructions.Jump;
import org.dynjs.ir.instructions.Return;
import org.dynjs.ir.operands.Undefined;
import org.dynjs.ir.representations.CFG.EdgeType;
/**
* This produces a linear list of BasicBlocks so that the linearized instruction
* list is in executable form. In generating this list, we will also add jumps
* where required and remove as many jumps as possible.
*
* Ordinary BasicBlocks will follow FollowThrough edges and just concatenate
* together eliminating the need for executing a jump instruction during
* execution.
*
* Notes:
* 1. Basic blocks ending in branches have two edges (FollowTrough/NotTaken and Taken)
* 2. All BasicBlocks can possibly have two additional edges related to exceptions:
* - one that transfers control to a rescue block (if one exists that protects
* the excepting instruction) which is also responsible for running ensures
* - one that transfers control to an ensure block (if one exists) for
* situations where we bypass the rescue block (breaks and thread-kill).
* 3. Branch, Jump, Return, and Exceptions are all boundaries for BasicBlocks
* 4. Dummy Entry and Exit BasicBlocks exist in all CFGs
*/
public class CFGLinearizer {
public static List<BasicBlock> linearize(CFG cfg) {
List<BasicBlock> list = new ArrayList<BasicBlock>();
BitSet processed = new BitSet(cfg.size()); // Assumes all id's are used
linearizeInner(cfg, list, processed, cfg.getEntryBB());
verifyAllBasicBlocksProcessed(cfg, processed);
fixupList(cfg, list);
return list;
}
private static void linearizeInner(CFG cfg, List<BasicBlock> list,
BitSet processed, BasicBlock current) {
if (processed.get(current.getID())) return;
// Cannot lay out current block till its fall-through predecessor has been laid out already
BasicBlock source = cfg.getIncomingSourceOfType(current, EdgeType.FALL_THROUGH);
if (source != null && !processed.get(source.getID())) return;
list.add(current);
processed.set(current.getID());
// First, fall-through BB
BasicBlock fallThrough = cfg.getOutgoingDestinationOfType(current, EdgeType.FALL_THROUGH);
if (fallThrough != null) linearizeInner(cfg, list, processed, fallThrough);
// Next, regular edges
for (BasicBlock destination: cfg.getOutgoingDestinationsOfType(current, EdgeType.REGULAR)) {
linearizeInner(cfg, list, processed, destination);
}
// Next, exception edges
for (BasicBlock destination: cfg.getOutgoingDestinationsOfType(current, EdgeType.EXCEPTION)) {
linearizeInner(cfg, list, processed, destination);
}
// Next, exit
for (BasicBlock destination: cfg.getOutgoingDestinationsOfType(current, EdgeType.EXIT)) {
linearizeInner(cfg, list, processed, destination);
}
}
/**
* Process (fixup) list of instruction and add or remove jumps.
*/
private static void fixupList(CFG cfg, List<BasicBlock> list) {
int n = list.size();
for (int i = 0; i < n - 1; i++) {
BasicBlock current = list.get(i);
if (current.isExitBB()) { // exit not last
current.addInstr(new Return(Undefined.UNDEFINED));
continue;
}
Instruction lastInstr = current.getLastInstr();
if (lastInstr instanceof Jump) { // if jumping to next BB then remove it
tryAndRemoveUnneededJump(list.get(i + 1), cfg, lastInstr, current);
} else {
addJumpIfNextNotDestination(cfg, list.get(i + 1), lastInstr, current);
}
}
BasicBlock current = list.get(n - 1);
if (!current.isExitBB()) {
Instruction lastInstr = current.getLastInstr();
// Last instruction of the last basic block in the linearized list can NEVER
// be a branch instruction because this basic block would then have a fallthrough
// which would have to be present after it.
assert (!(lastInstr instanceof Branch));
if ((lastInstr == null) || !lastInstr.transfersControl()) {
// We are guaranteed to have at least one non-exception edge because
// the exit BB post-dominates all BBs in the CFG even when exception
// edges are removed.
//
// Verify that we have exactly one non-exception target
Iterator<BasicBlock> iter = cfg.getOutgoingDestinationsNotOfType(current, EdgeType.EXCEPTION).iterator();
BasicBlock target = iter.next();
assert (target != null && !iter.hasNext());
// System.out.println("BB " + curr.getID() + " is the last bb in the layout! Adding a jump to " + tgt._label);
current.addInstr(new Jump(target.getLabel()));
}
}
}
private static void tryAndRemoveUnneededJump(BasicBlock next, CFG cfg, Instruction lastInstr, BasicBlock current) {
if (next == cfg.getBBForLabel(((Jump) lastInstr).getTarget())) current.removeInstr(lastInstr);
}
// If there is no jump at add of block and the next block is not destination insert a valid jump
private static void addJumpIfNextNotDestination(CFG cfg, BasicBlock next, Instruction lastInstr, BasicBlock current) {
Iterator<BasicBlock> outs = cfg.getOutgoingDestinations(current).iterator();
BasicBlock target = outs.hasNext() ? outs.next() : null;
if (target != null && !outs.hasNext()) {
if ((target != next) && ((lastInstr == null) || !lastInstr.transfersControl())) {
current.addInstr(new Jump(target.getLabel()));
}
}
}
private static void verifyAllBasicBlocksProcessed(CFG cfg, BitSet processed) throws RuntimeException {
// Verify that all bbs have been laid out!
for (BasicBlock b : cfg.getBasicBlocks()) {
if (!processed.get(b.getID())) {
throw new RuntimeException("Bad CFG linearization: BB " + b.getID() + " has been missed!");
}
}
}
}