blob: de0c42b0f98dd18b311646ac054bfb9e33a14b64 [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.royale.abc.semantics;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.royale.abc.ABCConstants;
import org.apache.royale.abc.graph.IBasicBlock;
import org.apache.royale.abc.graph.IFlowgraph;
import org.apache.royale.abc.graph.algorithms.DepthFirstPreorderIterator;
import org.apache.royale.abc.graph.algorithms.DominatorTree;
import org.apache.royale.abc.visitors.IFlowGraphVisitor;
/**
* A ControlFlowGraph represents the flow of control through a sequence of
* instructions. The instructions are organized into a set of Blocks --
* sequences of instructions where normal control flow proceeds linearly from
* one instruction to the next -- and a set of edges, representing the
* discontinuous transfer of control points.
*/
public class ControlFlowGraph implements IFlowgraph
{
/**
* Search direction in the initial block of a block-by-block search
* (typically for debug information).
*/
private enum SearchDirection { Forward, Backward };
/**
* Create a ControlFlowGraph from the instructions in the MethodBodyInfo.
* @param mbi - the MethodBodyInfo of the method whose control flow graph is to be computed.
*/
ControlFlowGraph(MethodBodyInfo mbi)
{
this.mbi = mbi;
this.startBlock = newBlock();
buildCfg();
}
/**
* This ControlFlowGraph's MethodBodyInfo.
*/
private final MethodBodyInfo mbi;
/**
* Blocks of the flow graph in entry order.
*/
private ArrayList<IBasicBlock> blocks = new ArrayList<IBasicBlock>();
/**
* The entry point of this routine; known in
* graph theory as the start block.
*/
private Block startBlock;
/**
* Blocks keyed by their Labels. Multiple Labels may map to a Block.
*/
private Map<Label, IBasicBlock> blocksByLabel = new HashMap<Label, IBasicBlock>();
/**
* Catch targets defined in this method.
*/
private ArrayList<IBasicBlock> catchTargets = new ArrayList<IBasicBlock>();
/**
* The graph's dominator tree, built on demand.
*/
private DominatorTree dominatorTree = null;
/**
* Build the CFG.
*/
private void buildCfg()
{
Block current_block = startBlock;
boolean last_block_transferred_control = false;
// Sort the labels by position.
Collections.sort(mbi.getInstructionList().getActiveLabels());
Iterator<Label> labels = mbi.getInstructionList().getActiveLabels().iterator();
Label next_label = getNextLabel(labels);
// Labels of a Block's successors, keyed by the "from" block.
// These are turned into edges from the "from" block once all
// blocks have been seen.
Map<Block, Collection<Label>> successor_labels = new HashMap<Block, Collection<Label>>();
// Iterate through the method's instructions by position; the active Labels
// have embedded position information that corresponds to this zero-based
// enumeration of the instructions.
List<Instruction> instructions = mbi.getInstructionList().getInstructions();
for (int i = 0; i < instructions.size(); i++)
{
if (i == next_label.getPosition() || last_block_transferred_control)
{
if ( current_block.size() > 0 )
{
Block prev_block = current_block;
current_block = newBlock();
if (prev_block.canFallThrough())
{
// Add an edge from the prev block to the current block, since
// control can fall through from that block to the current one.
prev_block.addSuccessor(current_block);
}
}
else
{
// If the first instruction is a label target,
// then the start block will still be empty;
// this is actually OK for the flowgraph, but
// would create a special case empty block
// and complicate clients' block processing.
assert current_block == startBlock;
}
// Add catch targets to the CFG's mapping.
if (i == next_label.getPosition() && this.mbi.isCatchTarget(next_label))
this.catchTargets.add(current_block);
// Map all labels targeting this block.
while (next_label.getPosition() == i)
{
blocksByLabel.put(next_label, current_block);
next_label = getNextLabel(labels);
}
}
Instruction insn = instructions.get(i);
current_block.add(insn);
// Begin a new block after any transfer of control.
last_block_transferred_control = insn.isTransferOfControl();
if (insn.isBranch())
{
Collection<Label> successors = successor_labels.get(current_block);
if (successors == null)
{
successors = new ArrayList<Label>();
successor_labels.put(current_block, successors);
}
// The target may be a forward jump to a block we haven't seen,
// just store the label for now, and we'll get its block later after
// we've seen all the instructions.
if (insn.getOpcode() == ABCConstants.OP_lookupswitch)
{
for (int j = 0; j < insn.getOperandCount(); j++)
{
if (insn.getOperand(j) instanceof Label)
successors.add((Label)insn.getOperand(j));
}
}
else
{
successors.add(insn.getTarget());
}
}
}
// We've seen all the instructions now, so we can compute the blocks that
// each label corresponds to, and fill in the rest of the graph
for (Map.Entry<Block, Collection<Label>> entry : successor_labels.entrySet())
for (Label target_label : entry.getValue())
{
IBasicBlock target_block = getBlock(target_label);
if ( target_block != null )
entry.getKey().addSuccessor(target_block);
}
}
/**
* Get a Label's target Block.
*
* @param l - the Label of interest.
* @return the corresponding Block, or null if not found.
*/
public IBasicBlock getBlock(Label l)
{
return blocksByLabel.get(l);
}
/**
* Get the start block.
* @return the start block.
*/
public IBasicBlock getStartBlock()
{
return this.startBlock;
}
/**
* Is the given Block a catch target?
* @param b - the Block of interest.
* @return true if the Block is a catch target.
* @see MethodBodyInfo#isCatchTarget(Label)
* which differs from this routine in that it uses
* positional information, which is not valid if the
* ControlFlowGraph has been edited.
*/
public boolean isCatchTarget(IBasicBlock b)
{
return this.catchTargets.contains(b);
}
/**
* Get an iterator that will iterate over the blocks in the graph.
* in depth-first preorder. This will traverse each edge in the graph
* once, but may return the same block multiple times if multiple edges lead
* to it.
*/
public Iterable<IBasicBlock> blocksInControlFlowOrder()
{
return new Iterable<IBasicBlock>()
{
@Override
public Iterator<IBasicBlock> iterator()
{
return new DepthFirstPreorderIterator(ControlFlowGraph.this.getRoots());
}
};
}
/**
* Start a new block.
*
* @return the new Block.
*/
private Block newBlock()
{
Block result = new Block();
blocks.add(result);
return result;
}
/**
* @param labels - an Iterator of Labels.
* @return the next Label in the sequence, or a marker
* Label if the sequence is exhausted.
*/
private Label getNextLabel(Iterator<Label> labels)
{
if (labels.hasNext())
return labels.next();
else
return END_OF_LABEL_SEQUENCE;
}
private static final Label END_OF_LABEL_SEQUENCE = new Label();
/**
* Get the graph's blocks in their original order.
*
* @return an immutable List that presents the blocks in entry order.
*/
public List<IBasicBlock> getBlocksInEntryOrder()
{
return Collections.unmodifiableList(this.blocks);
}
/**
* Get the graph's blocks as a mutable list.
* @return the blocks in a list.
*/
List<IBasicBlock> getBlocks()
{
return this.blocks;
}
/**
* Walk a IFlowGraphVisitor over this CFG.
*
* @param visitor - the visitor.
*/
public void traverseGraph(IFlowGraphVisitor visitor)
{
for (IBasicBlock b : this.blocksInControlFlowOrder())
{
if (visitor.visitBlock(b))
{
for (Instruction i : b.getInstructions())
visitor.visitInstruction(i);
visitor.visitEnd(b);
}
}
}
/**
* Touch the graph's dominator tree and fetch it.
* @return the graph's dominator tree.
*/
public DominatorTree getDominatorTree()
{
if ( this.dominatorTree == null )
this.dominatorTree = new DominatorTree(getRoots());
return this.dominatorTree;
}
/**
* Synthesize this flowgraph's "roots." The flowgraph actually
* has only one root, the start block, but the exception handlers
* aren't reachable by normal control flow so for the moment they're
* considered alternate roots.
* @return the Collection of the start block and any exception handlers.
*/
private Collection<IBasicBlock> getRoots()
{
Collection<IBasicBlock> roots = new ArrayList<IBasicBlock>();
roots.add(this.startBlock);
roots.addAll(this.catchTargets);
return roots;
}
/**
* Remove an unreachable block from the CFG.
* @param b - the Block to remove.
* @pre b must be unreachable.
*/
public void removeUnreachableBlock(IBasicBlock b)
{
assert(!isReachable(b));
// The CFG is naive about reachability
// of exception-handling targets, so
// this procedure may remove blocks
// that were formerly "reachable."
boolean removedFormerlyReachable = false;
// Edit any exception-handlers that reference b.
for ( ExceptionInfo ex: this.mbi.getExceptions() )
{
if ( b.equals(getBlock(ex.getFrom())) )
{
if ( b.equals(getBlock(ex.getTo())) )
{
// This exception-handler is now dead.
ex.setLive(false);
this.catchTargets.remove(getBlock(ex.getTarget()));
removedFormerlyReachable = true;
}
else
{
// Move the mapping of the From label
// to the next block in the covered region.
int bIdx = this.blocks.indexOf(b);
assert bIdx >= 0 && bIdx < this.blocks.size();
this.blocksByLabel.put(ex.getFrom(), this.blocks.get(bIdx+1));
}
}
else if ( b.equals(getBlock(ex.getTo())) )
{
if ( b.equals(getBlock(ex.getFrom())) )
{
// This exception-handler is now dead.
ex.setLive(false);
this.catchTargets.remove(getBlock(ex.getTarget()));
removedFormerlyReachable = true;
}
else
{
// Move the mapping of the To label
// to the previous block in the covered region.
int bIdx = this.blocks.indexOf(b);
assert bIdx >= 1 && bIdx < this.blocks.size();
this.blocksByLabel.put(ex.getTo(), this.blocks.get(bIdx-1));
}
}
}
if ( removedFormerlyReachable )
{
// The dominator tree needs to be recomputed.
this.dominatorTree = null;
}
// Finally, remove the block itself.
this.blocks.remove(b);
}
/**
* Is the given Block reachable?
* @param b - the block of interest.
* @return true if a path exists from
* any "entry" block to b.
*/
public boolean isReachable(IBasicBlock b)
{
return getDominatorTree().topologicalTraversal().contains(b);
}
/**
* Find the closest matching line number to the start of a block.
* @param b the block of interest.
* @return any initial debugline within the block, or the nearest
* debugline in the preceeding (entry-order) blocks.
*/
public int findLineNumber(IBasicBlock b)
{
return findLineNumber(b, 0, SearchDirection.Forward);
}
/**
* Find the nearest debugline instruction preceeding the given
* (Block,offset) position and fetch its line number.
* @param b - the Block of interest.
* @param initialOffset - the start offset in the block.
* @return the closest debugline instruction's line number,
* or -1 if not found.
*/
public int findLineNumber(IBasicBlock b, int initialOffset)
{
return findLineNumber(b, b.size()-1, SearchDirection.Backward);
}
/**
* Find the nearest debugline instruction preceeding the given
* (Block,offset) position and fetch its line number.
* @param b - the Block of interest.
* @param initialOffset - the start offset in the block.
* @param initialDirection Search the first block Forward or Backward.
* @return the closest debugline instruction's line number,
* or -1 if not found.
*/
private int findLineNumber(IBasicBlock b, int initialOffset, SearchDirection initialDirection)
{
assert(this.blocks.contains(b));
// Start searching in the block given; the offset may
// be a pseudo-offset that specifies an initial forward
// search through non-executable instructions.
Instruction result = searchBlock(b, ABCConstants.OP_debugline, initialOffset, initialDirection);
if ( result != null )
{
// Found a good line number match; return it as a zero-based offset.
return result.getImmediate() - 1;
}
else
{
// Search backwards through the instruction stream; bump any line
// number found during this search since the dead block is probably
// on the next line.
for ( int i = this.blocks.indexOf(b) - 1; i >= 0 && result == null; i-- )
{
IBasicBlock candidate = this.blocks.get(i);
result = searchBlock( candidate, ABCConstants.OP_debugline, candidate.size() - 1, SearchDirection.Backward);
}
}
return result != null? result.getImmediate(): -1;
}
/**
* Find the nearest debugfile instruction to the start of
* the given block and fetch its source path.
* @param b - the Block of interest.
* @return the closest debugfile instruction's source path,
* or null if not found.
*/
public String findSourcePath(IBasicBlock b)
{
return findSourcePath(b, 0, SearchDirection.Forward);
}
/**
* Find the nearest instruction preceeding the given
* (Block,offset) position and fetch its source path.
* @param b - the Block of interest.
* @param initialOffset - the start offset in the block.
* @return the closest debugfile instruction's source path,
* or null if not found.
*/
public String findSourcePath(IBasicBlock b, int initialOffset)
{
return findSourcePath(b, initialOffset, SearchDirection.Backward);
}
/**
* Find the nearest debugline instruction to the given (Block, offset)
* position and fetch its source path.
* @param b the block of interest.
* @param initialOffset the start offset in the block.
* @param initialDirection Search the first block Forward or Backward.
* @return the closest debugfile instruction's source path,
* or null if not found.
*/
private String findSourcePath(IBasicBlock b, int initialOffset, SearchDirection initialDirection)
{
assert(this.blocks.contains(b));
Instruction result = searchBlock(b, ABCConstants.OP_debugfile, initialOffset, initialDirection);
// Search backwards through the instruction stream if necessary.
if ( result == null )
{
for ( int i = this.blocks.indexOf(b) - 1; i >= 0 && result == null; i-- )
{
IBasicBlock candidate = this.blocks.get(i);
result = searchBlock(candidate, ABCConstants.OP_debugfile, candidate.size() - 1, SearchDirection.Backward);
}
}
if (result == null)
return null;
// The debug filename can sometimes be in the format: sourcepath;package;filename so
// need to translate that back to an absolute path by replacing the ;'s with system
// path separators, making sure to normailize path separators.
String debugFilename = result.getOperand(0).toString();
char otherSeparator = File.separatorChar == '/' ? '\\' : '/';
debugFilename = debugFilename.replace(otherSeparator, File.separatorChar);
return debugFilename.replace(';', File.separatorChar);
}
/**
* Find an instruction in the given block, working
* forwards or backwards from the specified offset.
* @param b the block of interest.
* @param opcode the opcode of the desired Instruction.
* @param initialOffset the offset at which to begin searching.
* @param direction Forward or Backward.
* @return the first instruction encountered with the specified
* opcode, or null if none found or the offset is out of range.
*/
private Instruction searchBlock(IBasicBlock b, int opcode, int initialOffset, SearchDirection direction)
{
Instruction result = null;
int blockSize = b.size();
assert initialOffset >= 0 && initialOffset < blockSize: String.format("invalid initialOffset %d", initialOffset);
int offset = initialOffset;
while ( result == null && offset >= 0 && offset < blockSize )
{
Instruction candidate = b.get(offset);
if ( candidate.getOpcode() == opcode )
result = candidate;
else if ( direction == SearchDirection.Forward )
offset++;
else
offset--;
}
return result;
}
}