CFGLinearizer.java

  1. package org.jruby.ir.representations;

  2. import org.jruby.ir.instructions.BranchInstr;
  3. import org.jruby.ir.instructions.Instr;
  4. import org.jruby.ir.instructions.JumpInstr;
  5. import org.jruby.ir.instructions.ReturnInstr;

  6. import java.util.ArrayList;
  7. import java.util.BitSet;
  8. import java.util.Iterator;
  9. import java.util.List;

  10. import static org.jruby.ir.representations.CFG.EdgeType.*;

  11. /**
  12.  * This produces a linear list of BasicBlocks so that the linearized instruction
  13.  * list is in executable form.  In generating this list, we will also add jumps
  14.  * where required and remove as many jumps as possible.
  15.  *
  16.  * Ordinary BasicBlocks will follow FollowThrough edges and just concatenate
  17.  * together eliminating the need for executing a jump instruction during
  18.  * execution.
  19.  *
  20.  * Notes:
  21.  * 1. Basic blocks ending in branches have two edges (FollowTrough/NotTaken and Taken)
  22.  * 2. All BasicBlocks can possibly have two additional edges related to exceptions:
  23.  *    - one that transfers control to a rescue block (if one exists that protects
  24.  *      the excepting instruction) which is also responsible for running ensures
  25.  *    - one that transfers control to an ensure block (if one exists) for
  26.  *      situations where we bypass the rescue block (breaks and thread-kill).
  27.  * 3. Branch, Jump, Return, and Exceptions are all boundaries for BasicBlocks
  28.  * 4. Dummy Entry and Exit BasicBlocks exist in all CFGs
  29.  *
  30.  * NOTE: When the IR builder first builds its list, and the CFG builder builds the CFG,
  31.  * the order in which BBs are created should already be a linearized list.  Need to verify
  32.  * this and we might be able to skip linearization if the CFG has not been transformed
  33.  * by any code transformation passes.  This might be the case when JRuby first starts up
  34.  * when we may just build the IR and start interpreting it right away without running any
  35.  * opts.  In that scenario, it may be worth it to not run the linearizer at all.
  36.  */
  37. public class CFGLinearizer {
  38.     public static List<BasicBlock> linearize(CFG cfg) {
  39.         List<BasicBlock> list = new ArrayList<BasicBlock>();
  40.         BitSet processed = new BitSet(cfg.size()); // Assumes all id's are used

  41.         linearizeInner(cfg, list, processed, cfg.getEntryBB());
  42.         verifyAllBasicBlocksProcessed(cfg, processed);
  43.         fixupList(cfg, list);

  44.         return list;
  45.     }

  46.     private static void linearizeInner(CFG cfg, List<BasicBlock> list,
  47.             BitSet processed, BasicBlock current) {
  48.         if (processed.get(current.getID())) return;

  49.         // Cannot lay out current block till its fall-through predecessor has been laid out already
  50.         BasicBlock source = cfg.getIncomingSourceOfType(current, FALL_THROUGH);
  51.         if (source != null && !processed.get(source.getID())) return;

  52.         list.add(current);
  53.         processed.set(current.getID());

  54.         // First, fall-through BB
  55.         BasicBlock fallThrough = cfg.getOutgoingDestinationOfType(current, FALL_THROUGH);
  56.         if (fallThrough != null) linearizeInner(cfg, list, processed, fallThrough);

  57.         // Next, regular edges
  58.         for (BasicBlock destination: cfg.getOutgoingDestinationsOfType(current, REGULAR)) {
  59.             linearizeInner(cfg, list, processed, destination);
  60.         }

  61.         // Next, exception edges
  62.         for (BasicBlock destination: cfg.getOutgoingDestinationsOfType(current, EXCEPTION)) {
  63.             linearizeInner(cfg, list, processed, destination);
  64.         }

  65.         // Next, exit
  66.         for (BasicBlock destination: cfg.getOutgoingDestinationsOfType(current, EXIT)) {
  67.             linearizeInner(cfg, list, processed, destination);
  68.         }
  69.     }

  70.     /**
  71.      * Process (fixup) list of instruction and add or remove jumps.
  72.      */
  73.     private static void fixupList(CFG cfg, List<BasicBlock> list) {
  74.         int n = list.size();
  75.         for (int i = 0; i < n - 1; i++) {
  76.             BasicBlock current = list.get(i);

  77.             if (current.isExitBB()) { // exit not last
  78.                 current.addInstr(new ReturnInstr(cfg.getScope().getManager().getNil()));
  79.                 continue;
  80.             }

  81.             Instr lastInstr = current.getLastInstr();
  82.             if (lastInstr instanceof JumpInstr) { // if jumping to next BB then remove it
  83.                 tryAndRemoveUnneededJump(list.get(i + 1), cfg, lastInstr, current);
  84.             } else {
  85.                 addJumpIfNextNotDestination(cfg, list.get(i + 1), lastInstr, current);
  86.             }
  87.         }

  88.         BasicBlock current = list.get(n - 1);
  89.         if (!current.isExitBB()) {
  90.             Instr lastInstr = current.getLastInstr();
  91.             // Last instruction of the last basic block in the linearized list can NEVER
  92.             // be a branch instruction because this basic block would then have a fallthrough
  93.             // which would have to be present after it.
  94.             assert (!(lastInstr instanceof BranchInstr));

  95.             if ((lastInstr == null) || !lastInstr.getOperation().transfersControl()) {
  96.                 // We are guaranteed to have at least one non-exception edge because
  97.                 // the exit BB post-dominates all BBs in the CFG even when exception
  98.                 // edges are removed.
  99.                 //
  100.                 // Verify that we have exactly one non-exception target
  101.                 // SSS FIXME: Is this assertion any different from the BranchInstr assertion above?
  102.                 Iterator<BasicBlock> iter = cfg.getOutgoingDestinationsNotOfType(current, EXCEPTION).iterator();
  103.                 BasicBlock target = iter.next();
  104.                 assert (target != null && !iter.hasNext());

  105.                 // System.out.println("BB " + curr.getID() + " is the last bb in the layout! Adding a jump to " + tgt._label);
  106.                 current.addInstr(new JumpInstr(target.getLabel()));
  107.             }
  108.         }
  109.     }

  110.     private static void tryAndRemoveUnneededJump(BasicBlock next, CFG cfg, Instr lastInstr, BasicBlock current) {
  111.         if (next == cfg.getBBForLabel(((JumpInstr) lastInstr).getJumpTarget())) current.removeInstr(lastInstr);
  112.     }

  113.     // If there is no jump at add of block and the next block is not destination insert a valid jump
  114.     private static void addJumpIfNextNotDestination(CFG cfg, BasicBlock next, Instr lastInstr, BasicBlock current) {
  115.         Iterator<BasicBlock> outs = cfg.getOutgoingDestinations(current).iterator();
  116.         BasicBlock target = outs.hasNext() ? outs.next() : null;

  117.         if (target != null && !outs.hasNext()) {
  118.             if ((target != next) && ((lastInstr == null) || !lastInstr.getOperation().transfersControl())) {
  119.                 current.addInstr(new JumpInstr(target.getLabel()));
  120.             }
  121.         }
  122.     }

  123.     private static void verifyAllBasicBlocksProcessed(CFG cfg, BitSet processed) throws RuntimeException {
  124.         // Verify that all bbs have been laid out!
  125.         for (BasicBlock b : cfg.getBasicBlocks()) {
  126.             if (!processed.get(b.getID())) {
  127.                 throw new RuntimeException("Bad CFG linearization: BB " + b.getID() + " has been missed!");
  128.             }
  129.         }
  130.     }
  131. }