/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* You may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.jikesrvm.compilers.opt.bc2ir;
import static org.jikesrvm.compilers.opt.bc2ir.IRGenOptions.DBG_BB;
import static org.jikesrvm.compilers.opt.bc2ir.IRGenOptions.DBG_BBSET;
import static org.jikesrvm.compilers.opt.bc2ir.IRGenOptions.DBG_CFG;
import static org.jikesrvm.compilers.opt.bc2ir.IRGenOptions.DBG_EX;
import static org.jikesrvm.compilers.opt.bc2ir.IRGenOptions.DBG_FLATTEN;
import static org.jikesrvm.compilers.opt.bc2ir.IRGenOptions.DBG_INLINE_JSR;
import static org.jikesrvm.compilers.opt.bc2ir.IRGenOptions.DBG_LOCAL;
import static org.jikesrvm.compilers.opt.bc2ir.IRGenOptions.DBG_REGEN;
import static org.jikesrvm.compilers.opt.bc2ir.IRGenOptions.DBG_STACK;
import static org.jikesrvm.compilers.opt.bc2ir.IRGenOptions.MAX_RETURN_ADDRESSES;
import static org.jikesrvm.compilers.opt.driver.OptConstants.RECTIFY_BCI;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.NoSuchElementException;
import org.jikesrvm.VM;
import org.jikesrvm.classloader.BytecodeStream;
import org.jikesrvm.classloader.ExceptionHandlerMap;
import org.jikesrvm.classloader.TypeReference;
import org.jikesrvm.compilers.opt.OperationNotImplementedException;
import org.jikesrvm.compilers.opt.ir.BasicBlock;
import org.jikesrvm.compilers.opt.ir.ExceptionHandlerBasicBlock;
import org.jikesrvm.compilers.opt.ir.ExceptionHandlerBasicBlockBag;
import org.jikesrvm.compilers.opt.ir.IRTools;
import org.jikesrvm.compilers.opt.ir.Instruction;
import org.jikesrvm.compilers.opt.ir.Move;
import org.jikesrvm.compilers.opt.ir.operand.Operand;
import org.jikesrvm.compilers.opt.ir.operand.RegisterOperand;
import org.jikesrvm.compilers.opt.ir.operand.TypeOperand;
/**
* Encapsulates the discovery and maintenance of the set of basic blocks that
* are being generated during construction of the IR.
* <p>
* The backing data store is a red/black tree, but there are a number of
* very specialized operations that are performed during search/insertion
* so we roll our own instead of using one from the standard library.
*/
final class BBSet {
/** root of the backing red/black tree*/
private BasicBlockLE root;
/**
* is it the case that we can ignore JSR processing because
* BC2IR has not yet generated a JSR bytecode in this method?
*/
private boolean noJSR = true;
/** entry block of the CFG */
private final BasicBlockLE entry;
/** associated generation context */
private final GenerationContext gc;
/** associated bytecodes */
private final BytecodeStream bcodes;
// Fields to support generation/identification of catch blocks
/** Start bytecode index for each exception handler ranges */
private int[] startPCs;
/** End bytecode index for each exception handler range */
private int[] endPCs;
/** Start bytecode index of each the exception handler */
private int[] handlerPCs;
/** Type of exception handled by each exception handler range. */
private TypeOperand[] exceptionTypes;
/**
* Initialize the BBSet to handle basic block generation for the argument
* generation context and bytecode info.
* @param gc the generation context to generate blocks for
* @param bcodes the bytecodes of said generation context
* @param localState the state of the local variables for the block
* beginning at bytecode 0.
*/
BBSet(GenerationContext gc, BytecodeStream bcodes, Operand[] localState) {
this.gc = gc;
this.bcodes = bcodes;
// Set up internal data structures to deal with exception handlers
parseExceptionTables();
// Create the entry block, setting root as a sideffect.
entry = _createBBLE(0, null, null, false);
entry.setStackKnown();
entry.copyIntoLocalState(localState);
}
BasicBlockLE getEntry() {
return entry;
}
/**
* Notify the BBSet that BC2IR has encountered a JSR bytecode.
* This enables more complex logic in getOrCreateBlock to drive
* the basic block specialization that is the key to JSR inlining.
*/
void seenJSR() {
noJSR = false;
}
/**
* @return a enumeration of the BasicBlockLE's currently in the BBSet
*/
Enumeration<BasicBlockLE> contents() {
return TreeEnumerator.enumFromRoot(root);
}
/**
* Gets the bytecode index of the block in the set which has the
* next-higher bytecode index.
*
* @param x basic block to start at.
* @return the bytecode index of the block in the set with the
* next-higher bytecode index. If {@code x} is currently the block with
* the highest starting bytecode index, return {@code bcodes.length()}.
*/
int getNextBlockBytecodeIndex(BasicBlockLE x) {
BasicBlockLE nextBBLE = getSuccessor(x, x.low);
return nextBBLE == null ? bcodes.length() : nextBBLE.low;
}
/**
* Finds the next ungenerated block, starting at the argument
* block and searching forward, wrapping around to the beginning.
*
* @param start the basic block at which to start looking.
* @return {@code null} if all blocks are generated, the next
* ungenerated block otherwise
*/
BasicBlockLE getNextEmptyBlock(BasicBlockLE start) {
if (DBG_BBSET) db("getting the next empty block after " + start);
// Look for an ungenerated block after start.
BBSet.TreeEnumerator e = TreeEnumerator.enumFromNode(start);
while (e.hasMoreElements()) {
BasicBlockLE block = e.next();
if (DBG_BBSET) {
db("Considering block " + block + " " + block.genState());
}
if (block.isReadyToGenerate()) {
if (DBG_BBSET) db("block " + block + " is not yet generated");
return block;
}
}
// There were none. Start looking from the beginning.
if (DBG_BBSET) db("at end of bytecodes, restarting at beginning");
e = TreeEnumerator.enumFromRoot(root);
while (true) {
BasicBlockLE block = e.next();
if (block == start) {
if (DBG_BBSET) db("wrapped around, no more empty blocks");
return null;
}
if (DBG_BBSET) {
db("Considering block " + block + " " + block.genState());
}
if (block.isReadyToGenerate()) {
if (DBG_BBSET) db("block " + block + " is not yet generated");
return block;
}
}
}
/**
* Get or create a block at the specified target.
* If simStack is non-{@code null}, rectifies stack state with target stack state.
* If simLocals is non-{@code null}, rectifies local state with target local state.
* Any instructions needed to rectify stack/local state are appended to
* from.
*
* @param target target index
* @param from the block from which control is being transfered
* and to which rectification instructions are added.
* @param simStack stack state to rectify, or {@code null}
* @param simLocals local state to rectify, or {@code null}
* @return a block, never {@code null}
*/
BasicBlockLE getOrCreateBlock(int target, BasicBlockLE from, OperandStack simStack, Operand[] simLocals) {
if (DBG_BB || BC2IR.DBG_SELECTED) {
db("getting block " +
target +
", match stack: " +
(simStack != null) +
" match locals: " +
(simLocals != null));
}
return getOrCreateBlock(root, true, target, from, simStack, simLocals);
}
/**
* Mark a previously generated block for regeneration.
* We define this method here so that in the future
* we can implement a more efficient getNextEmptyBlock that
* (1) avoids generating lots of blocks when a CFG predecessor has a
* pending regeneration and (2) avoids the scan through all blocks when
* there are no more blocks left to generate.
*
* @param p the block to mark for regeneration
*/
private void markBlockForRegeneration(BasicBlockLE p) {
if (DBG_REGEN) db("marking " + p + " for regeneration");
if (p.fallThrough != null && p.fallThrough instanceof InliningBlockLE) {
// if the fallthrough out edge of this block is an
// InlineMethodBasicBlock, then the inlined method must also be
// regenerated. In preparation for this, we must delete all out
// edges from the inlined method to the caller.
// (These arise from thrown/caught exceptions.)
InliningBlockLE imbb = (InliningBlockLE) p.fallThrough;
imbb.deleteAllOutEdges();
}
// discard any "real" instructions in the block
if (!p.block.isEmpty()) {
p.block.discardInstructions();
}
p.setSelfRegen();
p.clearGenerated();
p.fallThrough = null;
// If p had a non-empty stack on entry, we need to go through it
// and copy all of its operands (they may point to instructions
// we just blew away, but then again they may not (may not be in p),
// so we can't simply null out the instruction field);
if (p.stackState != null) {
int i = p.stackState.getSize();
while (i-- > 0) {
Operand op = p.stackState.getFromTop(i);
p.stackState.replaceFromTop(i, op.copy());
}
}
}
/**
* Rectify the given stack state with the state contained in the given
* BBLE, adding the necessary move instructions to the end of the given
* basic block to make register numbers agree and rectify mis-matched constants.
* <p>
* @param block basic block to append move instructions to
* @param stack stack to copy
* @param p BBLE to copy stack state into
*/
void rectifyStacks(BasicBlock block, OperandStack stack, BasicBlockLE p) {
if (stack == null || stack.isEmpty()) {
if (VM.VerifyAssertions) VM._assert(p.stackState == null);
if (!p.isStackKnown()) {
p.setStackKnown();
}
if (DBG_STACK || BC2IR.DBG_SELECTED) {
db("Rectified empty expression stack into " + p + "(" + p.block + ")");
}
return;
}
boolean generated = p.isGenerated();
// (1) Rectify the stacks.
if (!p.isStackKnown()) {
// First time we reached p. Thus, its expression stack
// is implicitly top and the meet degenerates to a copy operation
// with possibly some register renaming.
// (We need to ensure that non-local registers appear at
// most once on each expression stack).
if (DBG_STACK || BC2IR.DBG_SELECTED) {
db("First stack rectifiction for " + p + "(" + p.block + ") simply saving");
}
if (VM.VerifyAssertions) VM._assert(p.stackState == null);
p.stackState = stack.createEmptyOperandStackWithSameCapacity();
for (int i = stack.getSize() - 1; i >= 0; i--) {
Operand op = stack.getFromTop(i);
if (op == BC2IR.DUMMY) {
p.stackState.push(BC2IR.DUMMY);
} else if (op instanceof RegisterOperand) {
RegisterOperand rop = op.asRegister();
if (rop.getRegister().isLocal()) {
RegisterOperand temp = gc.getTemps().makeTemp(rop);
temp.setInheritableFlags(rop);
BC2IR.setGuardForRegOp(temp, BC2IR.copyGuardFromOperand(rop));
Instruction move = Move.create(IRTools.getMoveOp(rop.getType()), temp, rop.copyRO());
move.setSourcePosition(RECTIFY_BCI, gc.getInlineSequence());
block.appendInstructionRespectingTerminalBranch(move);
p.stackState.push(temp.copy());
if (DBG_STACK || BC2IR.DBG_SELECTED) {
db("Inserted " + move + " into " + block + " to rename local");
}
} else {
p.stackState.push(rop.copy());
}
} else {
p.stackState.push(op.copy());
}
}
p.setStackKnown();
} else {
// A real rectification.
// We need to update mergedStack such that
// mergedStack[i] = meet(mergedStack[i], stack[i]).
if (DBG_STACK || BC2IR.DBG_SELECTED) db("rectifying stacks");
try {
if (VM.VerifyAssertions) {
VM._assert(stack.getSize() == p.stackState.getSize());
}
} catch (NullPointerException e) {
System.err.println("stack size " + stack.getSize());
System.err.println(stack);
System.err.println(p.stackState);
System.err.println(gc.getMethod().toString());
block.printExtended();
p.block.printExtended();
throw e;
}
for (int i = 0; i < stack.getSize(); ++i) {
Operand sop = stack.getFromTop(i);
Operand mop = p.stackState.getFromTop(i);
if ((sop == BC2IR.DUMMY) || (sop instanceof ReturnAddressOperand)) {
if (VM.VerifyAssertions) VM._assert(mop.similar(sop));
continue;
} else if (sop.isConstant() || mop.isConstant()) {
if (mop.similar(sop)) {
continue; // constants are similar; so we don't have to do anything.
}
// sigh. Non-similar constants.
if (mop.isConstant()) {
// Insert move instructions in all predecessor
// blocks except 'block' to move mop into a register.
RegisterOperand mopTmp = gc.getTemps().makeTemp(mop);
if (DBG_STACK || BC2IR.DBG_SELECTED) db("Merged stack has constant operand " + mop);
for (Enumeration<BasicBlock> preds = p.block.getIn(); preds.hasMoreElements();) {
BasicBlock pred = preds.nextElement();
if (pred == block) continue;
injectMove(pred, mopTmp.copyRO(), mop.copy());
}
p.stackState.replaceFromTop(i, mopTmp.copy());
if (generated) {
if (DBG_STACK || BC2IR.DBG_SELECTED) {
db("\t...forced to regenerate " + p + " (" + p.block + ") because of this");
}
markBlockForRegeneration(p);
generated = false;
p.block.deleteOut();
if (DBG_CFG || BC2IR.DBG_SELECTED) db("Deleted all out edges of " + p.block);
}
mop = mopTmp;
}
if (sop.isConstant()) {
// Insert move instruction into block.
RegisterOperand sopTmp = gc.getTemps().makeTemp(sop);
if (DBG_STACK || BC2IR.DBG_SELECTED) db("incoming stack has constant operand " + sop);
injectMove(block, sopTmp, sop);
sop = sopTmp.copyRO();
}
}
// sop and mop are RegisterOperands (either originally or because
// we forced them to be above due to incompatible constants.
RegisterOperand rsop = sop.asRegister();
RegisterOperand rmop = mop.asRegister();
if (rmop.getRegister() != rsop.getRegister()) {
// must insert move at end of block to get register #s to match
RegisterOperand temp = rsop.copyRO();
temp.setRegister(rmop.getRegister());
injectMove(block, temp, rsop.copyRO());
}
Operand meet = Operand.meet(rmop, rsop, rmop.getRegister());
if (DBG_STACK || BC2IR.DBG_SELECTED) db("Meet of " + rmop + " and " + rsop + " is " + meet);
if (meet != rmop) {
if (generated) {
if (DBG_STACK || BC2IR.DBG_SELECTED) {
db("\t...forced to regenerate " + p + " (" + p.block + ") because of this");
}
markBlockForRegeneration(p);
generated = false;
p.block.deleteOut();
if (DBG_CFG || BC2IR.DBG_SELECTED) db("Deleted all out edges of " + p.block);
}
p.stackState.replaceFromTop(i, meet);
}
}
}
}
private void injectMove(BasicBlock block, RegisterOperand res, Operand val) {
Instruction move = Move.create(IRTools.getMoveOp(res.getType()), res, val);
move.setSourcePosition(RECTIFY_BCI, gc.getInlineSequence());
block.appendInstructionRespectingTerminalBranch(move);
if (DBG_STACK || BC2IR.DBG_SELECTED) {
db("Inserted " + move + " into " + block);
}
}
/**
* Rectify the given local variable state with the local variable state
* stored in the given BBLE.
*
* @param localState local variable state to rectify
* @param p target BBLE to rectify state to
*/
void rectifyLocals(Operand[] localState, BasicBlockLE p) {
if (!p.isLocalKnown()) {
if (DBG_LOCAL || BC2IR.DBG_SELECTED) {
db("rectifying with heretofore unknown locals, changing to save");
}
p.copyIntoLocalState(localState);
return;
}
if (DBG_LOCAL || BC2IR.DBG_SELECTED) db("rectifying current local state with " + p);
boolean generated = p.isGenerated();
Operand[] incomingState = localState;
Operand[] presentState = p.localState;
if (VM.VerifyAssertions) {
VM._assert(incomingState.length == presentState.length);
}
for (int i = 0, n = incomingState.length; i < n; ++i) {
Operand pOP = presentState[i];
Operand iOP = incomingState[i];
if (pOP == iOP) {
if (DBG_LOCAL || BC2IR.DBG_SELECTED) {
db("local states have the exact same operand " + pOP + " for local " + i);
}
} else {
boolean untyped = (pOP == null || pOP == BC2IR.DUMMY || pOP instanceof ReturnAddressOperand);
Operand mOP = Operand.meet(pOP, iOP, untyped ? null : gc.localReg(i, pOP.getType()));
if (DBG_LOCAL || BC2IR.DBG_SELECTED) db("Meet of " + pOP + " and " + iOP + " is " + mOP);
if (mOP != pOP) {
if (generated) {
if (DBG_LOCAL || BC2IR.DBG_SELECTED) {
db("\t...forced to regenerate " + p + " (" + p.block + ") because of this");
}
markBlockForRegeneration(p);
generated = false;
p.block.deleteOut();
if (DBG_CFG || BC2IR.DBG_SELECTED) db("Deleted all out edges of " + p.block);
}
presentState[i] = mOP;
}
}
}
}
/**
* Do a final pass over the generated basic blocks to create
* the initial code ordering. All blocks generated for the method
* will be inserted after gc.prologue.
* <p>
* NOTE: Only some CFG edges are created here.....
* we're mainly just patching together a code linearization.
*
* @param inlinedSomething was a normal method (i.e. non-magic) inlined?
*/
void finalPass(boolean inlinedSomething) {
BBSet.TreeEnumerator e = TreeEnumerator.enumFromRoot(root);
BasicBlock cop = gc.getPrologue();
BasicBlockLE curr = getEntry();
BasicBlockLE next = null;
top:
while (true) {
// Step 0: If curr is the first block in a catch block,
// inject synthetic entry block too.
if (curr instanceof HandlerBlockLE) {
// tell our caller that we actually put a handler in the final CFG.
gc.markExceptionHandlersAsGenerated();
HandlerBlockLE hcurr = (HandlerBlockLE) curr;
if (DBG_FLATTEN) {
db("injecting handler entry block " + hcurr.entryBlock + " before " + hcurr);
}
gc.getCfg().insertAfterInCodeOrder(cop, hcurr.entryBlock);
cop = hcurr.entryBlock;
}
// Step 1: Insert curr in the code order (after cop, updating cop).
if (DBG_FLATTEN) db("flattening: " + curr + " (" + curr.block + ")");
curr.setInCodeOrder();
gc.getCfg().insertAfterInCodeOrder(cop, curr.block);
cop = curr.block;
if (DBG_FLATTEN) {
db("Current Code order for " + gc.getMethod() + "\n");
for (BasicBlock bb = gc.getPrologue(); bb != null; bb = (BasicBlock) bb.getNext()) {
VM.sysWriteln(bb.toString());
}
}
// Step 1.1 Sometimes (rarely) there will be an inscope
// exception handler that wasn't actually generated. If this happens,
// make a new, filtered EHBBB to avoid later confusion.
if (curr.handlers != null) {
int notGenerated = 0;
for (HandlerBlockLE handler : curr.handlers) {
if (!handler.isGenerated()) {
if (DBG_EX || DBG_FLATTEN) {
db("Will remove unreachable handler " + handler + " from " + curr);
}
notGenerated++;
}
}
if (notGenerated > 0) {
if (notGenerated == curr.handlers.length) {
if (DBG_EX || DBG_FLATTEN) {
db("No (local) handlers were actually reachable for " + curr + "; setting to caller");
}
curr.block.setExceptionHandlers(curr.block.exceptionHandlers().getCaller());
} else {
ExceptionHandlerBasicBlock[] nlh =
new ExceptionHandlerBasicBlock[curr.handlers.length - notGenerated];
for (int i = 0, j = 0; i < curr.handlers.length; i++) {
if (curr.handlers[i].isGenerated()) {
nlh[j++] = curr.handlers[i].entryBlock;
} else {
if (VM.VerifyAssertions) {
VM._assert(curr.handlers[i].entryBlock.hasZeroIn(), "Non-generated handler with CFG edges");
}
}
}
curr.block.setExceptionHandlers(new ExceptionHandlerBasicBlockBag(nlh, curr.block.exceptionHandlers().getCaller()));
}
}
}
// Step 2: Identify the next basic block to add to the code order.
// curr wants to fallthrough to an inlined method.
// Inject the entire inlined CFG in the code order.
// There's some fairly complicated coordination between this code,
// GenerationContext, and maybeInlineMethod. Sorry, but you'll
// have to take a close look at all of these to see how it
// all fits together....--dave
if (curr.fallThrough != null && curr.fallThrough instanceof InliningBlockLE) {
InliningBlockLE icurr = (InliningBlockLE) curr.fallThrough;
BasicBlock forw = cop.nextBasicBlockInCodeOrder();
BasicBlock calleeEntry = icurr.gc.getCfg().firstInCodeOrder();
BasicBlock calleeExit = icurr.gc.getCfg().lastInCodeOrder();
gc.getCfg().breakCodeOrder(cop, forw);
gc.getCfg().linkInCodeOrder(cop, icurr.gc.getCfg().firstInCodeOrder());
gc.getCfg().linkInCodeOrder(icurr.gc.getCfg().lastInCodeOrder(), forw);
if (DBG_CFG || BC2IR.DBG_SELECTED) {
db("Added CFG edge from " + cop + " to " + calleeEntry);
}
if (icurr.epilogueBBLE != null) {
if (DBG_FLATTEN) {
db("injected " + icurr + " between " + curr + " and " + icurr.epilogueBBLE.fallThrough);
}
if (VM.VerifyAssertions) {
VM._assert(icurr.epilogueBBLE.block == icurr.gc.getCfg().lastInCodeOrder());
}
curr = icurr.epilogueBBLE;
cop = curr.block;
} else {
if (DBG_FLATTEN) db("injected " + icurr + " after " + curr);
curr = icurr;
cop = calleeExit;
}
}
next = curr.fallThrough;
if (DBG_FLATTEN && next == null) {
db(curr + " has no fallthrough case, getting next block");
}
if (next != null) {
if (DBG_CFG || BC2IR.DBG_SELECTED) {
db("Added CFG edge from " + curr.block + " to " + next.block);
}
if (next.isInCodeOrder()) {
if (DBG_FLATTEN) {
db("fallthrough " + next + " is already flattened, adding goto");
}
curr.block.appendInstruction(next.block.makeGOTO());
// set next to null to indicate no "real" fall through
next = null;
}
}
if (next == null) {
// Can't process fallthroughblock, so get next BBLE from enumeration
while (true) {
if (!e.hasMoreElements()) {
// all done.
if (DBG_FLATTEN) db("no more blocks! all done");
break top;
}
next = e.next();
if (DBG_FLATTEN) db("looking at " + next);
if (!next.isGenerated()) {
if (DBG_FLATTEN) db("block " + next + " was not generated");
continue;
}
if (!next.isInCodeOrder()) {
break;
}
}
if (DBG_FLATTEN) db("found unflattened block: " + next);
}
curr = next;
}
// If the epilogue was unreachable, remove it from the code order and cfg
// and set gc.epilogue to null.
boolean removedSomethingFromCodeOrdering = inlinedSomething;
if (gc.getEpilogue().hasZeroIn()) {
if (DBG_FLATTEN || DBG_CFG) {
db("Deleting unreachable epilogue " + gc.getEpilogue());
}
gc.getCfg().removeFromCodeOrder(gc.getEpilogue());
removedSomethingFromCodeOrdering = true;
// remove the node from the graph AND adjust its edge info
gc.getEpilogue().remove();
gc.getEpilogue().deleteIn();
gc.getEpilogue().deleteOut();
if (VM.VerifyAssertions) VM._assert(gc.getEpilogue().hasZeroOut());
gc.setEpilogue(null);
}
// if gc has an unlockAndRethrow block that was not used, then remove it
if (gc.getUnlockAndRethrow() != null && gc.getUnlockAndRethrow().hasZeroIn()) {
gc.getCfg().removeFromCFGAndCodeOrder(gc.getUnlockAndRethrow());
removedSomethingFromCodeOrdering = true;
gc.getEnclosingHandlers().remove(gc.getUnlockAndRethrow());
}
// if we removed a basic block then we should compact the node numbering
if (removedSomethingFromCodeOrdering) {
gc.getCfg().compactNodeNumbering();
}
if (DBG_FLATTEN) {
db("Current Code order for " + gc.getMethod() + "\n");
for (BasicBlock bb = gc.getPrologue(); bb != null; bb = (BasicBlock) bb.getNext()) {
bb.printExtended();
}
}
if (DBG_FLATTEN) {
db("Final CFG for " + gc.getMethod() + "\n");
gc.getCfg().printDepthFirst();
}
}
//////////////////////////////////////////
// Gory implementation details of BBSet //
//////////////////////////////////////////
/**
* Print a debug string to the sysWrite stream.
* @param val string to print
*/
private void db(String val) {
VM.sysWriteln("IRGEN " + bcodes.getDeclaringClass() + "." + gc.getMethod().getName() + ":" + val);
}
/**
* Initializes the global exception handler arrays for the method.
*/
private void parseExceptionTables() {
ExceptionHandlerMap eMap = gc.getMethod().getExceptionHandlerMap();
if (DBG_EX) db("\texception handlers for " + gc.getMethod() + ": " + eMap);
if (eMap == null) return; // method has no exception handling ranges.
startPCs = eMap.getStartPC();
endPCs = eMap.getEndPC();
handlerPCs = eMap.getHandlerPC();
int numExceptionHandlers = startPCs.length;
exceptionTypes = new TypeOperand[numExceptionHandlers];
for (int i = 0; i < numExceptionHandlers; i++) {
exceptionTypes[i] = new TypeOperand(eMap.getExceptionType(i));
if (DBG_EX) db("\t\t[" + startPCs[i] + "," + endPCs[i] + "] " + eMap.getExceptionType(i));
}
}
/**
* Initialize bble's handlers array based on startPCs/endPCs.
* In the process, new HandlerBlockLE's may be created
* (by the call to getOrCreateBlock). <p>
* PRECONDITION: bble.low and bble.max have already been correctly
* set to reflect the invariant that a basic block is in exactly one
* "handler range."<p>
* Also initializes bble.block.exceptionHandlers.
*
* @param bble the block whose handlers are to be initialized
* @param simLocals local variables
*/
private void initializeExceptionHandlers(BasicBlockLE bble, Operand[] simLocals) {
if (startPCs != null) {
HashSet<TypeReference> caughtTypes = new HashSet<TypeReference>();
for (int i = 0; i < startPCs.length; i++) {
TypeReference caughtType = exceptionTypes[i].getTypeRef();
if (bble.low >= startPCs[i] && bble.max <= endPCs[i] && !caughtTypes.contains(caughtType)) {
// bble's basic block is contained within this handler's range.
HandlerBlockLE eh = (HandlerBlockLE) getOrCreateBlock(handlerPCs[i], bble, null, simLocals);
if (DBG_EX) db("Adding handler " + eh + " to " + bble);
caughtTypes.add(caughtType);
bble.addHandler(eh);
}
}
}
if (bble.handlers != null) {
ExceptionHandlerBasicBlock[] ehbbs = new ExceptionHandlerBasicBlock[bble.handlers.length];
for (int i = 0; i < bble.handlers.length; i++) {
ehbbs[i] = bble.handlers[i].entryBlock;
}
bble.block.setExceptionHandlers(new ExceptionHandlerBasicBlockBag(ehbbs, gc.getEnclosingHandlers()));
} else {
bble.block.setExceptionHandlers(gc.getEnclosingHandlers());
}
}
/**
* Given a starting bytecode index, find the greatest bcIndex that
* is still has the same inscope exception handlers.
* @param bcIndex the start bytecode index
* @return greatest bytecode index with the same in scope exception
* handlers
*/
private int exceptionEndRange(int bcIndex) {
int max = bcodes.length();
if (startPCs != null) {
for (int spc : startPCs) {
if (bcIndex < spc && max > spc) {
max = spc;
}
}
for (int epc : endPCs) {
if (bcIndex < epc && max > epc) {
max = epc;
}
}
}
return max;
}
/**
* We specialize basic blocks with respect to the return addresses
* they have on their expression stack and/or in their local variables
* on entry to the block. This has the effect of inlining the
* subroutine body at all of the JSR sites that invoke it.
* This is the key routine: it determines whether or not the
* argument simState (stack and locals) contains compatible
* return addresses as the candidate BasicBlockLE.
* <p>
* The main motivation for inlining away all JSR's is that it eliminates
* the "JSR problem" for type accurate GC. It is also simpler to
* implement and arguably results in more efficient generated code
* (assuming that we don't get horrific code bloat).
* To deal with the code bloat, we detect excessive code duplication and
* stop IR generation (bail out to the baseline compiler).
*
* @param simStack The expression stack to match
* @param simLocals The local variables to match
* @param candBBLE The candidate BaseicBlockLE
* @return {@code true} if and only if a matching JSR context (see above)
* was found
*/
private boolean matchingJSRcontext(OperandStack simStack, Operand[] simLocals, BasicBlockLE candBBLE) {
if (DBG_INLINE_JSR) {
db("Matching JSR context of argument stack/locals against " + candBBLE);
}
int numRA = 0;
if (simStack != null && candBBLE.isStackKnown()) {
for (int i = simStack.getSize() - 1; i >= 0; i--) {
Operand op = simStack.getFromTop(i);
if (op instanceof ReturnAddressOperand) {
if (numRA++ > MAX_RETURN_ADDRESSES) {
throw new OperationNotImplementedException("Too many subroutines");
}
if (DBG_INLINE_JSR) db("simStack operand " + i + " is " + op);
Operand cop = candBBLE.stackState.getFromTop(i);
if (!Operand.conservativelyApproximates(cop, op)) {
if (DBG_INLINE_JSR) db("Not Matching: " + cop + " and " + op);
return false;
} else {
if (DBG_INLINE_JSR) db("operand " + cop + " is compatible with " + op);
}
}
}
}
if (simLocals != null && candBBLE.isLocalKnown()) {
for (int i = 0; i < simLocals.length; i++) {
Operand op = simLocals[i];
if (op instanceof ReturnAddressOperand) {
if (numRA++ > MAX_RETURN_ADDRESSES) {
throw new OperationNotImplementedException("Too many subroutines");
}
if (DBG_INLINE_JSR) db("simLocal " + i + " is " + op);
Operand cop = candBBLE.localState[i];
if (!Operand.conservativelyApproximates(cop, op)) {
if (DBG_INLINE_JSR) db("Not Matching: " + cop + " and " + op);
return false;
} else {
if (DBG_INLINE_JSR) db("operand " + cop + " is compatible with " + op);
}
}
}
}
if (DBG_INLINE_JSR) db("Found " + candBBLE + " to be compatible");
return true;
}
/**
* Get or create a block at the specified target.
* If simStack is non-{@code null}, rectifies stack state with target stack state.
* If simLocals is non-{@code null}, rectifies local state with target local state.
* Any instructions needed to rectify stack/local state are appended to
* from.
* As blocks are created, they are added to the red/black tree below x.
*
* @param x starting node for search.
* @param shouldCreate should we create the block if we run off the tree?
* @param target target index
* @param from the block from which control is being transfered
* and to which rectification instructions are added.
* @param simStack stack state to rectify, or {@code null}
* @param simLocals local state to rectify, or {@code null}
* @return a new block, never {@code null}
*/
private BasicBlockLE getOrCreateBlock(BasicBlockLE x, boolean shouldCreate, int target, BasicBlockLE from,
OperandStack simStack, Operand[] simLocals) {
if (target < x.low) {
if (x.left == null) {
return condCreateAndInit(x, shouldCreate, target, from, simStack, simLocals, true);
} else {
if (DBG_BBSET) db("following left branch from " + x + " to " + x.left);
return getOrCreateBlock(x.left, shouldCreate, target, from, simStack, simLocals);
}
} else if (target > x.low) {
if ((x.low < target) && (target <= x.high)) {
// the target points to the middle of x; mark x for regen
if (DBG_BBSET) db("target points to middle of " + x);
markBlockForRegeneration(x);
x.high = x.low;
x.block.deleteOut();
if (DBG_CFG || BC2IR.DBG_SELECTED) db("Deleted all out edges of " + x.block);
}
if (x.right == null) {
return condCreateAndInit(x, shouldCreate, target, from, simStack, simLocals, false);
} else {
if (DBG_BBSET) db("following right branch from " + x + " to " + x.right);
return getOrCreateBlock(x.right, shouldCreate, target, from, simStack, simLocals);
}
} else {
// found a basic block at the target bytecode index.
if (noJSR || matchingJSRcontext(simStack, simLocals, x)) {
if (DBG_BBSET) db("found block " + x + " (" + x.block + ")");
if (simStack != null) rectifyStacks(from.block, simStack, x);
if (simLocals != null) rectifyLocals(simLocals, x);
return x;
}
if (DBG_BBSET) db("found block " + x + ", but JSR context didn't match");
if (x.left == null) {
if (x.right == null) {
return condCreateAndInit(x, shouldCreate, target, from, simStack, simLocals, true);
} else {
if (DBG_BBSET) {
db(x + " has only right child, continuing down that branch");
}
return getOrCreateBlock(x.right, shouldCreate, target, from, simStack, simLocals);
}
} else {
if (x.right == null) {
if (DBG_BBSET) {
db(x + " has only left child, continuing down that branch");
}
return getOrCreateBlock(x.left, shouldCreate, target, from, simStack, simLocals);
} else {
if (DBG_BBSET) {
db(x + " has two children, searching left branch first");
}
BasicBlockLE bble = getOrCreateBlock(x.left, false, target, from, simStack, simLocals);
if (bble != null) {
return bble;
} else {
if (DBG_BBSET) {
db("didn't find " + target + " on left branch, continuing down right branch");
}
return getOrCreateBlock(x.right, shouldCreate, target, from, simStack, simLocals);
}
}
}
}
}
/**
* Conditionally create a block at the specified target as a child of x.
* If simStack is non-{@code null}, rectifies stack state with target stack state.
* If simLocals is non-{@code null}, rectifies local state with target local state.
* Any instructions needed to rectify stack/local state are appended to
* from.
*
* @param x starting node for search.
* @param shouldCreate should we create the block if we run off the tree?
* @param target target index
* @param from the block from which control is being transfered
* and to which rectification instructions are added.
* @param simStack stack state to rectify, or {@code null}
* @param simLocals local state to rectify, or {@code null}
* @param left are we creating a left child of parent?
* @return the newly created block, or {@code null} if !shouldCreate
*/
private BasicBlockLE condCreateAndInit(BasicBlockLE x, boolean shouldCreate, int target, BasicBlockLE from,
OperandStack simStack, Operand[] simLocals, boolean left) {
BasicBlockLE bble = null;
if (shouldCreate) {
bble = _createBBLE(target, simLocals, x, left);
if (simStack != null) {
rectifyStacks(from.block, simStack, bble);
}
if (simLocals != null) {
bble.copyIntoLocalState(simLocals);
}
}
return bble;
}
/**
* Allocate a new BBLE at the given bcIndex.
* If bcIndex is the start of an handler block,
* then a HandlerBlockLE is created.
* After the BBLE is created, its handlers data structure is initialized
* (which may cause other blocks to be created).
* @param bcIndex the bytecode index at which the block should be created.
* @param simLocals the localState to pass (via initializeExceptionHandler)to
* to getOrCreateBlock if we need to create BBLEs for
* exception handlers. This is only actually used if
* !noJSR. We don't need the expression stack, since
* the only thing on the expression stack on entry to
* a handler block is the exception object (and thus
* we can skip scanning the expression stack for
* return addresses when creating a handler block).
* @param parent parent in Red/Black tree
* @param left are we creating a left child of parent?
* @return a new BBLE
*/
private BasicBlockLE _createBBLE(int bcIndex, Operand[] simLocals, BasicBlockLE parent, boolean left) {
BasicBlockLE newBBLE = null;
if (handlerPCs != null) {
for (int i = 0; i < handlerPCs.length; i++) {
if (handlerPCs[i] == bcIndex) {
if (newBBLE == null) {
newBBLE =
new HandlerBlockLE(bcIndex,
gc.getInlineSequence(),
exceptionTypes[i],
gc.getTemps(),
gc.getMethod().getOperandWords(),
gc.getCfg());
((HandlerBlockLE) newBBLE).entryBlock.firstRealInstruction().setPosition(gc.getInlineSequence());
} else {
((HandlerBlockLE) newBBLE).addCaughtException(exceptionTypes[i]);
}
}
}
}
if (newBBLE == null) {
newBBLE = new BasicBlockLE(bcIndex, gc.getInlineSequence(), gc.getCfg());
}
// Set newBBLE.max to encode exception ranges
newBBLE.max = exceptionEndRange(bcIndex);
if (DBG_BBSET) db("Created " + newBBLE);
// Now, insert newBBLE into our backing Red/Black tree before we call
// initializeExceptionHandlers.
// We must do it in this order because initExHand may in turn call
// _createBBLE to create new handler blocks, and our tree must contain
// newBBLE before we can correctly insert another block.
treeInsert(parent, newBBLE, left);
initializeExceptionHandlers(newBBLE, simLocals);
return newBBLE;
}
/**
* Returns the basic block's sucessor in bytecode sequence.
*
* @param x basic block at which to start the search for a higher block
* @param value the contents of x.low (makes tail call elim work better
* if we avoid the obvious 1 argument wrapper function)
* @return {@code null} if x is the block with the highest bytecode index,
* the block with the next-higher bytecode index otherwise.
*/
private BasicBlockLE getSuccessor(BasicBlockLE x, int value) {
if (x.right != null) return minimumBB(x.right, value);
BasicBlockLE y = x.parent;
while ((y != null) && (x == y.right)) {
x = y;
y = x.parent;
}
// at this point either x is the root, or x is the left child of y
if ((y == null) || (y.low != value)) return y;
return getSuccessor(y, value);
}
private BasicBlockLE minimumBB(BasicBlockLE x, int value) {
if (x.left != null) return minimumBB(x.left, value);
if (value == x.low) return getSuccessor(x, value);
return x;
}
/**
* Insert <code>newBBLE</code> as a child of parent in our Red/Black tree.
* @param parent the parent node
* @param newBBLE the new child node
* @param left is the child the left or right child of parent?
*/
private void treeInsert(BasicBlockLE parent, BasicBlockLE newBBLE, boolean left) {
if (parent == null) {
if (VM.VerifyAssertions) VM._assert(root == null);
root = newBBLE;
root.setBlack();
if (DBG_BBSET) db("inserted " + newBBLE + " as root of tree");
} else {
if (left) {
parent.left = newBBLE;
} else {
parent.right = newBBLE;
}
newBBLE.parent = parent;
if (DBG_BBSET) {
db("inserted new block " + newBBLE + " as " + (left ? "left" : "right") + " child of " + parent);
}
fixupBBSet(newBBLE);
}
}
/**
* Performs tree fixup (restore Red/Black invariants) after adding a
* new node to the tree.
* @param x node that was added.
*/
private void fixupBBSet(BasicBlockLE x) {
if (DBG_BBSET) db("fixing up tree after inserting " + x);
x.setRed();
while (x != root) {
BasicBlockLE xp = x.parent;
if (xp.isBlack()) {
break;
}
if (DBG_BBSET) db(x + " and its parent " + xp + " are both red");
BasicBlockLE xpp = xp.parent;
if (DBG_BBSET) db(xp + "'s parent is " + xpp);
if (xp == xpp.left) {
BasicBlockLE y = xpp.right;
if ((y != null) && y.isRed()) {
xp.setBlack();
y.setBlack();
xpp.setRed();
x = xpp;
} else {
if (x == xp.right) {
x = xp;
leftRotateBBSet(xp);
xp = x.parent;
xpp = xp.parent;
}
xp.setBlack();
xpp.setRed();
rightRotateBBSet(xpp);
}
} else {
BasicBlockLE y = xpp.left;
if ((y != null) && y.isRed()) {
xp.setBlack();
y.setBlack();
xpp.setRed();
x = xpp;
} else {
if (x == xp.left) {
x = xp;
rightRotateBBSet(xp);
xp = x.parent;
xpp = xp.parent;
}
xp.setBlack();
xpp.setRed();
leftRotateBBSet(xpp);
}
}
}
root.setBlack();
// verifyTree();
}
private void leftRotateBBSet(BasicBlockLE x) {
if (DBG_BBSET) db("performing left tree rotation");
BasicBlockLE y = x.right;
BasicBlockLE yl = y.left;
x.right = yl;
if (yl != null) {
yl.parent = x;
}
BasicBlockLE xp = x.parent;
y.parent = xp;
if (xp == null) {
root = y;
} else if (x == xp.left) {
xp.left = y;
} else {
xp.right = y;
}
y.left = x;
x.parent = y;
}
private void rightRotateBBSet(BasicBlockLE x) {
if (DBG_BBSET) db("performing right tree rotation");
BasicBlockLE y = x.left;
BasicBlockLE yr = y.right;
x.left = yr;
if (yr != null) {
yr.parent = x;
}
BasicBlockLE xp = x.parent;
y.parent = xp;
if (xp == null) {
root = y;
} else if (x == xp.right) {
xp.right = y;
} else {
xp.left = y;
}
y.right = x;
x.parent = y;
}
@SuppressWarnings("unused")
// here for debugging
private void verifyTree() {
if (VM.VerifyAssertions) {
VM._assert(root.isBlack());
verifyTree(root, -1, bcodes.length());
countBlack(root);
}
}
private void verifyTree(BasicBlockLE node, int min, int max) {
if (VM.VerifyAssertions) {
VM._assert(node.low >= min);
VM._assert(node.low <= max);
if (node.left != null) {
VM._assert(node.isBlack() || node.left.isBlack());
VM._assert(node.left.parent == node);
verifyTree(node.left, min, node.low);
}
if (node.right != null) {
VM._assert(node.isBlack() || node.right.isBlack());
VM._assert(node.right.parent == node);
verifyTree(node.right, node.low, max);
}
}
}
private int countBlack(BasicBlockLE node) {
if (node == null) return 1;
int left = countBlack(node.left);
int right = countBlack(node.right);
if (VM.VerifyAssertions) VM._assert(left == right);
if (node.isBlack()) {
left++;
}
return left;
}
private static final class TreeEnumerator implements Enumeration<BasicBlockLE> {
BasicBlockLE node;
static BBSet.TreeEnumerator enumFromRoot(BasicBlockLE root) {
if (root.left != null) {
do {
root = root.left;
} while (root.left != null);
}
return new TreeEnumerator(root);
}
static BBSet.TreeEnumerator enumFromNode(BasicBlockLE node) {
return new TreeEnumerator(node);
}
private TreeEnumerator(BasicBlockLE node) {
this.node = node;
}
@Override
public boolean hasMoreElements() {
return (node != null);
}
public BasicBlockLE next() {
BasicBlockLE retVal = node;
if (retVal == null) {
throw new NoSuchElementException();
}
if (retVal.right != null) {
node = retVal.right;
while (node.left != null) {
node = node.left;
}
} else {
BasicBlockLE x = retVal;
node = x.parent;
while ((node != null) && (node.right == x)) {
x = node;
node = x.parent;
}
}
return retVal;
}
@Override
public BasicBlockLE nextElement() {
return next();
}
}
}