WALA/com.ibm.wala.core/src/com/ibm/wala/ssa/IR.java

752 lines
23 KiB
Java

/*******************************************************************************
* Copyright (c) 2002 - 2006 IBM Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package com.ibm.wala.ssa;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import com.ibm.wala.cfg.ControlFlowGraph;
import com.ibm.wala.classLoader.CallSiteReference;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.classLoader.NewSiteReference;
import com.ibm.wala.classLoader.ProgramCounter;
import com.ibm.wala.ssa.SSACFG.BasicBlock;
import com.ibm.wala.ssa.SSACFG.ExceptionHandlerBasicBlock;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.collections.CompoundIterator;
import com.ibm.wala.util.collections.HashMapFactory;
import com.ibm.wala.util.collections.HashSetFactory;
import com.ibm.wala.util.debug.Assertions;
import com.ibm.wala.util.intset.BasicNaturalRelation;
import com.ibm.wala.util.intset.IntIterator;
import com.ibm.wala.util.intset.IntSet;
import com.ibm.wala.util.strings.StringStuff;
/**
* An SSA IR.
*
* The IR (Intermediate Representation) is the central data structure that represents the instructions of a particular method.
* The IR represents a method's instructions in a language close to JVM bytecode, but in an SSA-based register transfer language
* which eliminates the stack abstraction, relying instead on a set of symbolic registers. The IR organizes instructions in a
* control-flow graph of basic blocks, as typical in compiler textbooks.
*
* See http://wala.sourceforge.net/wiki/index.php/UserGuide:IR for more details on the IR API.
*/
public abstract class IR implements IRView {
/**
* The method that defined this IR's bytecodes
*/
final private IMethod method;
/**
* Governing SSA construction options
*/
private final SSAOptions options;
/**
* Control-flow graph
*/
final private SSACFG cfg;
/**
* SSA instructions
*/
final private SSAInstruction[] instructions;
/**
* Symbol table
*/
final private SymbolTable symbolTable;
/**
* Mapping from CallSiteReference program counters to instruction[] indices
*/
private final BasicNaturalRelation callSiteMapping = new BasicNaturalRelation();
/**
* Mapping from NewSiteReference program counters to instruction[] indices
*/
private final Map<NewSiteReference, Integer> newSiteMapping = HashMapFactory.make();
/**
* Mapping from PEI program counters to instruction[] indices
*/
final private Map<ProgramCounter, Integer> peiMapping = HashMapFactory.make();
/**
* Mapping from SSAInstruction to Basic Block, computed lazily
*/
private Map<SSAInstruction, ISSABasicBlock> instruction2Block;
/**
* subclasses must provide a source name mapping, if they want one (or null otherwise)
*/
protected abstract SSA2LocalMap getLocalMap();
/**
* subclasses must provide information about indirect use of values, if appropriate, and otherwise null
*/
protected abstract <T extends SSAIndirectionData.Name> SSAIndirectionData<T> getIndirectionData();
/**
* Simple constructor when someone else has already computed the symbol table and cfg.
*/
protected IR(IMethod method, SSAInstruction[] instructions, SymbolTable symbolTable, SSACFG cfg, SSAOptions options) {
if (method == null) {
throw new IllegalArgumentException("method is null");
}
this.method = method;
this.instructions = instructions;
this.symbolTable = symbolTable;
this.cfg = cfg;
this.options = options;
}
/**
* create mappings from call sites, new sites, and PEIs to instruction index
*/
protected void setupLocationMap() {
for (int i = 0; i < instructions.length; i++) {
SSAInstruction x = instructions[i];
if (x != null) {
if (x instanceof SSAAbstractInvokeInstruction) {
callSiteMapping.add(((SSAAbstractInvokeInstruction) x).getCallSite().getProgramCounter(), i);
}
if (x instanceof SSANewInstruction) {
newSiteMapping.put(((SSANewInstruction) x).getNewSite(), new Integer(i));
}
if (x.isPEI()) {
peiMapping.put(new ProgramCounter(cfg.getProgramCounter(i)), new Integer(i));
}
}
}
}
/**
* @return a String which is a readable representation of the instruction position corresponding to an instruction index
*/
protected abstract String instructionPosition(int instructionIndex);
@Override
public String toString() {
Collection<? extends SSAIndirectionData.Name> names = null;
if (getIndirectionData() != null) {
names = getIndirectionData().getNames();
}
StringBuffer result = new StringBuffer(method.toString());
result.append("\nCFG:\n");
result.append(cfg.toString());
result.append("Instructions:\n");
for (int i = 0; i <= cfg.getMaxNumber(); i++) {
BasicBlock bb = cfg.getNode(i);
int start = bb.getFirstInstructionIndex();
int end = bb.getLastInstructionIndex();
result.append("BB").append(bb.getNumber());
if (bb instanceof ExceptionHandlerBasicBlock) {
result.append("<Handler> (");
Iterator<TypeReference> catchIter = ((ExceptionHandlerBasicBlock) bb).getCaughtExceptionTypes();
while (catchIter.hasNext()) {
TypeReference next = catchIter.next();
result.append(next);
if (catchIter.hasNext()) {
result.append(",");
}
}
result.append(")");
}
result.append("\n");
for (Iterator it = bb.iteratePhis(); it.hasNext();) {
SSAPhiInstruction phi = (SSAPhiInstruction) it.next();
if (phi != null) {
result.append(" " + phi.toString(symbolTable)).append("\n");
}
}
if (bb instanceof ExceptionHandlerBasicBlock) {
ExceptionHandlerBasicBlock ebb = (ExceptionHandlerBasicBlock) bb;
SSAGetCaughtExceptionInstruction s = ebb.getCatchInstruction();
if (s != null) {
result.append(" " + s.toString(symbolTable)).append("\n");
} else {
result.append(" " + " No catch instruction. Unreachable?\n");
}
}
for (int j = start; j <= end; j++) {
if (instructions[j] != null) {
if (names != null) {
boolean any = false;
for(SSAIndirectionData.Name n : names) {
if (getIndirectionData().getUse(j, n) != -1) {
result.append(" " + n + " -> " + getIndirectionData().getUse(j, n));
any = true;
}
}
if (any) {
result.append("\n");
}
}
StringBuffer x = new StringBuffer(j + " " + instructions[j].toString(symbolTable));
StringStuff.padWithSpaces(x, 45);
result.append(x);
result.append(instructionPosition(j));
Map<Integer,Set<String>> valNames = HashMapFactory.make();
for(int v = 0; v < instructions[j].getNumberOfDefs(); v++) {
int valNum = instructions[j].getDef(v);
addNames(j, valNames, valNum);
}
for(int v = 0; v < instructions[j].getNumberOfUses(); v++) {
int valNum = instructions[j].getUse(v);
addNames(j, valNames, valNum);
}
if (!valNames.isEmpty()) {
result.append(" ").append(valNames);
}
result.append("\n");
if (names != null) {
boolean any = false;
for(SSAIndirectionData.Name n : names) {
if (getIndirectionData().getDef(j, n) != -1) {
result.append(" " + n + " <- " + getIndirectionData().getDef(j, n));
any = true;
}
}
if (any) {
result.append("\n");
}
}
}
}
for (Iterator it = bb.iteratePis(); it.hasNext();) {
SSAPiInstruction pi = (SSAPiInstruction) it.next();
if (pi != null) {
result.append(" " + pi.toString(symbolTable)).append("\n");
}
}
}
return result.toString();
}
private void addNames(int j, Map<Integer, Set<String>> valNames, int valNum) {
if (getLocalNames(j, valNum) != null && getLocalNames(j, valNum).length > 0) {
if (! valNames.containsKey(valNum)) {
valNames.put(valNum, HashSetFactory.<String>make());
}
for(String s : getLocalNames(j, valNum)) {
valNames.get(valNum).add(s);
}
}
}
/**
* Returns the normal instructions. Does not include {@link SSAPhiInstruction}, {@link SSAPiInstruction}, or
* {@link SSAGetCaughtExceptionInstruction}s, which are currently managed by {@link BasicBlock}. Entries in the returned array
* might be null.
*
* This may go away someday.
*/
@Override
public SSAInstruction[] getInstructions() {
return instructions;
}
/**
* @return the {@link SymbolTable} managing attributes for values in this method
*/
@Override
public SymbolTable getSymbolTable() {
return symbolTable;
}
/**
* @return the underlying {@link ControlFlowGraph} which defines this IR.
*/
@Override
public SSACFG getControlFlowGraph() {
return cfg;
}
@Override
public Iterator<ISSABasicBlock> getBlocks() {
return getControlFlowGraph().iterator();
}
/**
* Return an {@link Iterator} of all {@link SSAPhiInstruction}s for this IR.
*/
public Iterator<? extends SSAInstruction> iteratePhis() {
return new TwoLevelIterator() {
@Override
Iterator<? extends SSAInstruction> getBlockIterator(BasicBlock b) {
return b.iteratePhis();
}
};
}
/**
* Return an {@link Iterator} of all {@link SSAPiInstruction}s for this IR.
*/
public Iterator<? extends SSAInstruction> iteratePis() {
return new TwoLevelIterator() {
@Override
Iterator<? extends SSAInstruction> getBlockIterator(BasicBlock b) {
return b.iteratePis();
}
};
}
/**
* An {@link Iterator} over all {@link SSAInstruction}s of a certain type, retrieved by iterating over the {@link BasicBlock}s,
* one at a time.
*
* TODO: this looks buggy to me .. looks like it's hardcoded for Phis. Does it work for Pis?
*/
abstract private class TwoLevelIterator implements Iterator<SSAInstruction> {
// invariant: if currentBlockIndex != -1, then
// currentBlockIterator.hasNext()
private Iterator<? extends SSAInstruction> currentBlockIterator;
private int currentBlockIndex;
abstract Iterator<? extends SSAInstruction> getBlockIterator(BasicBlock b);
TwoLevelIterator() {
currentBlockIndex = 0;
currentBlockIterator = cfg.getNode(0).iteratePhis();
if (!currentBlockIterator.hasNext()) {
advanceBlock();
}
}
@Override
public boolean hasNext() {
return currentBlockIndex != -1;
}
@Override
public SSAInstruction next() {
SSAInstruction result = currentBlockIterator.next();
if (!currentBlockIterator.hasNext()) {
advanceBlock();
}
return result;
}
@Override
public void remove() {
Assertions.UNREACHABLE();
}
private void advanceBlock() {
for (int i = currentBlockIndex + 1; i <= cfg.getMaxNumber(); i++) {
Iterator<? extends SSAInstruction> it = getBlockIterator(cfg.getNode(i));
if (it.hasNext()) {
currentBlockIndex = i;
currentBlockIterator = it;
return;
}
}
currentBlockIterator = null;
currentBlockIndex = -1;
}
}
/**
* @return array of value numbers representing parameters to this method
*/
public int[] getParameterValueNumbers() {
return symbolTable.getParameterValueNumbers();
}
/**
* @return the value number of the ith parameter
*/
public int getParameter(int i) {
return symbolTable.getParameter(i);
}
/**
* Get the {@link TypeReference} that describes the ith parameter to this method. By convention, for a non-static method, the 0th
* parameter is "this".
*/
public TypeReference getParameterType(int i) {
return method.getParameterType(i);
}
/**
* @return number of parameters to this method, including "this"
*/
public int getNumberOfParameters() {
return method.getNumberOfParameters();
}
/**
* @return the method this IR represents
*/
@Override
public IMethod getMethod() {
return method;
}
/**
* @return iterator of the catch instructions in this IR
*/
public Iterator<SSAInstruction> iterateCatchInstructions() {
return new CatchIterator();
}
/**
* TODO: looks like this should be merged into {@link TwoLevelIterator}, above?
*/
private class CatchIterator implements Iterator<SSAInstruction> {
// invariant: if currentBlockIndex != -1, then
// then block[currentBlockIndex] is a handler block
private int currentBlockIndex;
private boolean hasCatch(Object x) {
return (x instanceof ExceptionHandlerBasicBlock) && (((ExceptionHandlerBasicBlock) x).getCatchInstruction() != null);
}
CatchIterator() {
currentBlockIndex = 0;
if (!hasCatch(cfg.getNode(0))) {
advanceBlock();
}
}
@Override
public boolean hasNext() {
return currentBlockIndex != -1;
}
@Override
public SSAInstruction next() {
ExceptionHandlerBasicBlock bb = (ExceptionHandlerBasicBlock) cfg.getNode(currentBlockIndex);
SSAInstruction result = bb.getCatchInstruction();
advanceBlock();
return result;
}
@Override
public void remove() {
Assertions.UNREACHABLE();
}
private void advanceBlock() {
for (int i = currentBlockIndex + 1; i < cfg.getMaxNumber(); i++) {
if (hasCatch(cfg.getNode(i))) {
currentBlockIndex = i;
return;
}
}
currentBlockIndex = -1;
}
}
/**
* visit each normal (non-phi, non-pi, non-catch) instruction in this IR
*/
public void visitNormalInstructions(SSAInstruction.Visitor v) {
for (Iterator i = iterateNormalInstructions(); i.hasNext();) {
((SSAInstruction) i.next()).visit(v);
}
}
/**
* visit each instruction in this IR
*/
public void visitAllInstructions(SSAInstruction.Visitor v) {
for (Iterator i = iterateAllInstructions(); i.hasNext();) {
((SSAInstruction) i.next()).visit(v);
}
}
/**
* @return an {@link Iterator} of all "normal" instructions on this IR
*/
public Iterator<SSAInstruction> iterateNormalInstructions() {
return new NormalIterator();
}
private class NormalIterator implements Iterator<SSAInstruction> {
int nextIndex = -1;
final SSAInstruction[] instructions = getInstructions();
NormalIterator() {
advanceIndex(0);
}
private void advanceIndex(int start) {
for (int i = start; i < instructions.length; i++) {
if (instructions[i] != null) {
nextIndex = i;
return;
}
}
nextIndex = -1;
}
@Override
public boolean hasNext() {
return nextIndex != -1;
}
@Override
public void remove() {
Assertions.UNREACHABLE();
}
@Override
public SSAInstruction next() {
SSAInstruction result = instructions[nextIndex];
advanceIndex(nextIndex + 1);
return result;
}
}
/**
* @return an {@link Iterator} of all instructions (Normal, Phi, and Catch)
*/
public Iterator<SSAInstruction> iterateAllInstructions() {
return new CompoundIterator<SSAInstruction>(iterateNormalInstructions(), new CompoundIterator<SSAInstruction>(
iterateCatchInstructions(), new CompoundIterator<SSAInstruction>(iteratePhis(), iteratePis())));
}
/**
* @return the exit basic block
*/
@Override
public BasicBlock getExitBlock() {
return cfg.exit();
}
/**
* Return the invoke instructions corresponding to a call site
*
* Note that Shrike may inline JSRS. This can lead to multiple copies of a single bytecode instruction in a particular IR. So we
* may have more than one instruction index for a particular call site from bytecode.
*/
public SSAAbstractInvokeInstruction[] getCalls(CallSiteReference site) {
if (site == null) {
throw new IllegalArgumentException("site is null");
}
IntSet s = callSiteMapping.getRelated(site.getProgramCounter());
if (s == null) {
throw new IllegalArgumentException("no calls at site's pc");
}
SSAAbstractInvokeInstruction[] result = new SSAAbstractInvokeInstruction[s.size()];
int index = 0;
for (IntIterator it = s.intIterator(); it.hasNext();) {
int i = it.next();
result[index++] = (SSAAbstractInvokeInstruction) instructions[i];
}
return result;
}
/**
* Return the instruction indices corresponding to a call site.
*
* Note that Shrike may inline JSRS. This can lead to multiple copies of a single bytecode instruction in a particular IR. So we
* may have more than one instruction index for a particular call site from bytecode.
*/
public IntSet getCallInstructionIndices(CallSiteReference site) {
if (site == null) {
throw new IllegalArgumentException("site is null");
}
return callSiteMapping.getRelated(site.getProgramCounter());
}
/**
* Return the new instruction corresponding to an allocation site
*/
public SSANewInstruction getNew(NewSiteReference site) {
Integer i = newSiteMapping.get(site);
return (SSANewInstruction) instructions[i.intValue()];
}
/**
* Return the instruction index corresponding to an allocation site
*/
public int getNewInstructionIndex(NewSiteReference site) {
Integer i = newSiteMapping.get(site);
return i.intValue();
}
/**
* @param pc a program counter
* @return the instruction (a PEI) at this program counter
*/
@Override
public SSAInstruction getPEI(ProgramCounter pc) {
Integer i = peiMapping.get(pc);
return instructions[i.intValue()];
}
/**
* @return an {@link Iterator} of all the allocation sites ( {@link NewSiteReference}s ) in this IR
*/
@Override
public Iterator<NewSiteReference> iterateNewSites() {
return newSiteMapping.keySet().iterator();
}
/**
* @return an {@link Iterator} of all the call sites ( {@link CallSiteReference}s ) in this IR
*/
@Override
public Iterator<CallSiteReference> iterateCallSites() {
return new Iterator<CallSiteReference>() {
private final int limit = callSiteMapping.maxKeyValue();
private int i = -1;
{
advance();
}
private void advance() {
while (callSiteMapping.getRelatedCount(++i) == 0 && i <= limit)
;
}
@Override
public boolean hasNext() {
return i <= limit;
}
@Override
public CallSiteReference next() {
int index = callSiteMapping.getRelated(i).max();
advance();
return ((SSAAbstractInvokeInstruction) instructions[index]).getCallSite();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/**
* @param site a call site in this method
* @return the basic block corresponding to this instruction
* @throws IllegalArgumentException if site is null
*/
@Override
public ISSABasicBlock[] getBasicBlocksForCall(final CallSiteReference site) {
if (site == null) {
throw new IllegalArgumentException("site is null");
}
final IntSet s = callSiteMapping.getRelated(site.getProgramCounter());
if (s == null) {
throw new IllegalArgumentException("invalid site: " + site);
}
final ISSABasicBlock[] result = new ISSABasicBlock[s.size()];
int index = 0;
for (final IntIterator it = s.intIterator(); it.hasNext();) {
final int i = it.next();
result[index++] = getControlFlowGraph().getBlockForInstruction(i);
}
return result;
}
/**
* This is space-inefficient. Use with care.
*
* Be very careful; note the strange identity semantics of SSAInstruction, using ==. You can't mix SSAInstructions and IRs freely.
*/
public ISSABasicBlock getBasicBlockForInstruction(SSAInstruction s) {
if (instruction2Block == null) {
mapInstructions2Blocks();
}
return instruction2Block.get(s);
}
private void mapInstructions2Blocks() {
instruction2Block = HashMapFactory.make();
for (ISSABasicBlock b : cfg) {
for (SSAInstruction s : b) {
instruction2Block.put(s, b);
}
}
}
/**
* TODO: why do we need this? We should enforce instructions == null if necessary, I think.
*
* @return true iff every instruction is null
*/
public boolean isEmptyIR() {
if (instructions == null)
return true;
for (int i = 0; i < instructions.length; i++)
if (instructions[i] != null)
return false;
return true;
}
/**
* @param index an index into the IR instruction array
* @param vn a value number
* @return if we know that immediately after the given program counter, v_vn corresponds to one or more locals and local variable
* names are available, the name of the locals which v_vn represents. Otherwise, null.
*/
@Override
public String[] getLocalNames(int index, int vn) {
if (getLocalMap() == null) {
return new String[0];
} else {
return getLocalMap().getLocalNames(index, vn);
}
}
/**
* A Map that gives the names of the local variables corresponding to SSA value numbers at particular IR instruction indices, if
* such information is available from source code mapping.
*/
public interface SSA2LocalMap {
/**
* @param index an index into the IR instruction array
* @param vn a value number
* @return if we know that immediately after the given program counter, v_vn corresponds to one or more locals and local
* variable names are available, the name of the locals which v_vn represents. Otherwise, null.
*/
String[] getLocalNames(int index, int vn);
}
/**
* Return the {@link ISSABasicBlock} corresponding to a particular catch instruction
*/
public ISSABasicBlock getBasicBlockForCatch(SSAGetCaughtExceptionInstruction instruction) {
if (instruction == null) {
throw new IllegalArgumentException("instruction is null");
}
int bb = instruction.getBasicBlockNumber();
return cfg.getBasicBlock(bb);
}
/**
* @return the {@link SSAOptions} which controlled how this {@link IR} was built
*/
public SSAOptions getOptions() {
return options;
}
}