/* Copyright (c) 2006, Sriram Srinivasan
*
* You may distribute this software under the terms of the license
* specified in the file "License"
*/
package kilim.analysis;
import static kilim.Constants.D_ARRAY_BOOLEAN;
import static kilim.Constants.D_ARRAY_BYTE;
import static kilim.Constants.D_ARRAY_CHAR;
import static kilim.Constants.D_ARRAY_DOUBLE;
import static kilim.Constants.D_ARRAY_FLOAT;
import static kilim.Constants.D_ARRAY_INT;
import static kilim.Constants.D_ARRAY_LONG;
import static kilim.Constants.D_ARRAY_SHORT;
import static kilim.Constants.D_BOOLEAN;
import static kilim.Constants.D_BYTE;
import static kilim.Constants.D_CHAR;
import static kilim.Constants.D_DOUBLE;
import static kilim.Constants.D_FLOAT;
import static kilim.Constants.D_INT;
import static kilim.Constants.D_LONG;
import static kilim.Constants.D_NULL;
import static kilim.Constants.D_RETURN_ADDRESS;
import static kilim.Constants.D_SHORT;
import static kilim.Constants.D_VOID;
import static kilim.Constants.TASK_CLASS;
import static kilim.Constants.THROWABLE_CLASS;
import static org.objectweb.asm.Opcodes.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;
import kilim.KilimException;
import kilim.mirrors.Detector;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LookupSwitchInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MultiANewArrayInsnNode;
import org.objectweb.asm.tree.TableSwitchInsnNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
/**
* A basic block is a contiguous set of instructions that has one label at the
* first instruction and a transfer-of-control instruction at the very end. A
* transfer-of-control instruction includes all branching instructions that have
* labelled targets (IF_x, GOTO, and JSR) and the rest (ATHROW, xRETURN, RET).
* There can be no target labels in the middle of a basic block; in other words,
* you can't jump into the middle of a basic block. This is the standard
* definition; we make a few changes.
*
* <dl>
* <li>
* We create BasicBlocks whenever we encounter a label (in a linear
* scanning of a method's instructions. Some labels are meant for catch
* handlers and debug (line number) information only; they are not the
* target of a branching instruction, but we don't know that in the
* first pass. We coalesce those BasicBlocks that merely follow
* another, provided the preceding BB is the only preceder. Note that
* blocks connected with a GOTO can't coalesce because they are not
* likely to be contiguous, even if they obey the constraint of a
* single edge. We also don't coalesce blocks starting with a pausable method
* invocation with their predecessor, because we need these blocks to
* tell us about downstream usage of local vars to help us generate
* optimal continuations. </li>
*
* <li> All catch handlers that intersect a basic block are treated as
* successors to the block, for the purposes of liveness analysis.
*
* <li> Subroutines (targets of JSR) are treated specially. We inline all JSR
* calls, including nested JSRs, to simplify liveness analysis. In this phase, a
* JSR/RET is treated the same as a GOTO sub followed by a GOTO to the caller.
* During the weaving phase, we ignore the inlining information if the
* subroutine doesn't have any pausable methods. If it does, then we spit out
* duplicate code, complete with GOTOs as described above. This allows us to
* jump in the middle of a "finally" block during rewinding.
*
* Note: The JVM reference doesn't specify the boundaries of a JSR instruction;
* in other words, there is no definitive way of saying which blocks belong to a
* subroutine. This code treats the set of all nodes reachable via branching
* instructions from the subroutine's entry point. (exception catch blocks don't
* count) </li>
* </dl>
*/
public class BasicBlock implements Comparable<BasicBlock> {
/**
* A number handed out in increasing order of starting position, to ease
* sorting as well as for debug information
*/
public int id;
/*
* One of the bit flags above.
*/
int flags;
/*
* Used by the flow analysis algorithm to mark this BB as enqueued for
* processing
*/
static final int ENQUEUED = 1;
/*
* Used by the JSR inlining process to signify that a subroutine (a JSR
* target) has been claimed by a corresponding call. All other JSR calls
* pointing to this subroutine have to make their own duplicates.
*/
static final int SUBROUTINE_CLAIMED = 1 << 1;
/*
* Flag used by the consolidation process to avoid processing this block
* again.
*/
static final int COALESCED = 1 << 2;
/*
* Set if this BB contains a call to a pausable method
*/
static final int PAUSABLE = 1 << 4;
/*
* Set if this block is the entry point to a subroutine and the target of
* one or more JSR instructions
*/
static final int IS_SUBROUTINE = 1 << 5;
/*
* Set if this block belongs to a subroutine
*/
static final int SUB_BLOCK = 1 << 6;
/*
* Set by the subroutine inlining phase to avoid rechecking this BB.
*/
static final int INLINE_CHECKED = 1 << 7;
/*
* Set for the entry point to a subroutine that contains a pausable
* method. The entry point is the target of a JSR instruction.
*/
static final int PAUSABLE_SUB = 1 << 8;
/**
* The flow to which this BB belongs.
*/
public MethodFlow flow;
/**
* The label that starts this BB. In some cases we create a label where it
* didn't exist originally (after a jmp instruction, for example). This
* allows us a unique indexing scheme.
*/
public LabelNode startLabel;
/**
* Start and end points (both inclusive) in the current method's list of
* instructions (this.flow.instructions)
*/
public int startPos = -1;
public int endPos = -1;
/**
* List of successors (follower and all branch targets). Should be null
*/
public ArrayList<BasicBlock> successors = new ArrayList<BasicBlock>(3);
public ArrayList<Handler> handlers = new ArrayList<Handler>(2);
int numPredecessors;
/**
* usage initially contains the usage of local variables in this block
* (without reference to any other block). After flow analysis it contains
* the combined effect of this and all downstream blocks
*/
public Usage usage;
/**
* A cached version of all sucessors' usage, successors being catch handlers
* and real successors.
*/
ArrayList<Usage> succUsage;
/**
* The frame at the BB's entry point. It changes when propagating changes
* from its predeccessors, until there's a fixed point.
*/
public Frame startFrame;
/*
* If this BB is a catch block (the entry point to a series of catch handler
* blocks, it contains the type of the exception
*/
String caughtExceptionType;
/*
* The BB that follows this BB. Is null if the last instruction is a GOTO or
* THROW or RETURN or RET. The follower is also part of the successors list.
*/
BasicBlock follower;
/*
* sa subroutine, subBlocks contains the list of BBs that belong to it.
*/
ArrayList<BasicBlock> subBlocks;
public BasicBlock(MethodFlow aflow, LabelNode aStartLabel) {
flow = aflow;
startLabel = aStartLabel;
usage = new Usage(aflow.maxLocals);
successors = new ArrayList<BasicBlock>(2);
}
Detector detector() {
return flow.detector();
}
/**
* Absorb as many instructions until the next label or the next transfer of
* control instruction. In the first pass we may end up creating many many
* BBs because there may be a lot of non-target labels (esp. when debug
* information is available). The constraints are as follows:
* 1. A transfer of control instruction must be the last instruction. It
* may also be the first (and only) instruction
* 2. A labeled instruction must be the first instruction in a BB. It
* may optionally be the last (and only) instruction
* 3. A pausable method is treated like a labeled instruction, and is
* given a label if there isn't one already. Constraint 2 applies.
*/
@SuppressWarnings("unchecked")
int initialize(int pos) {
AbstractInsnNode ain;
startPos = pos;
BasicBlock bb;
boolean endOfBB = false;
boolean hasFollower = true;
int size = flow.instructions.size();
for (; pos < size; pos++) {
if (pos > startPos && flow.getLabelAt(pos) != null) {
pos--;
hasFollower = true;
endOfBB = true;
break;
}
ain = getInstruction(pos);
int opcode = ain.getOpcode();
switch (opcode) {
case ALOAD:
case ILOAD:
case LLOAD:
case FLOAD:
case DLOAD:
usage.read(((VarInsnNode) ain).var);
break;
case ISTORE:
case LSTORE:
case FSTORE:
case DSTORE:
case ASTORE:
usage.write(((VarInsnNode) ain).var);
break;
case IINC:
int v = ((IincInsnNode)ain).var;
usage.read(v);
usage.write(v);
break;
case IFEQ:
case IFNE:
case IFLT:
case IFGE:
case IFGT:
case IFLE:
case IFNULL:
case IFNONNULL:
case IF_ICMPEQ:
case IF_ICMPNE:
case IF_ICMPLT:
case IF_ICMPGE:
case IF_ICMPGT:
case IF_ICMPLE:
case IF_ACMPEQ:
case IF_ACMPNE:
case JSR:
case GOTO:
LabelNode l = ((JumpInsnNode) ain).label;
bb = flow.getOrCreateBasicBlock(l);
if (opcode == JSR) {
bb.setFlag(IS_SUBROUTINE);
hasFollower = false;
}
addSuccessor(bb);
if (opcode == GOTO) {
hasFollower = false;
}
endOfBB = true;
break;
case RET:
case IRETURN:
case LRETURN:
case FRETURN:
case DRETURN:
case ARETURN:
case RETURN:
case ATHROW:
hasFollower = false;
endOfBB = true;
break;
case TABLESWITCH:
case LOOKUPSWITCH:
LabelNode defaultLabel;
List<LabelNode> otherLabels;
if (opcode == TABLESWITCH) {
defaultLabel = ((TableSwitchInsnNode) ain).dflt;
otherLabels = ((TableSwitchInsnNode) ain).labels;
} else {
defaultLabel = ((LookupSwitchInsnNode) ain).dflt;
otherLabels = ((LookupSwitchInsnNode) ain).labels;
}
for (Iterator<LabelNode> it = otherLabels.iterator(); it.hasNext();) {
l = it.next();
addSuccessor(flow.getOrCreateBasicBlock(l));
}
addSuccessor(flow.getOrCreateBasicBlock(defaultLabel));
endOfBB = true;
hasFollower = false;
break;
case INVOKEVIRTUAL:
case INVOKESTATIC:
case INVOKEINTERFACE:
case INVOKESPECIAL:
if (flow.isPausableMethodInsn((MethodInsnNode) ain)) {
LabelNode il = flow.getOrCreateLabelAtPos(pos);
if (pos == startPos) {
setFlag(PAUSABLE);
} else {
bb = flow.getOrCreateBasicBlock(il);
bb.setFlag(PAUSABLE);
addSuccessor(bb);
pos--; // don't consume this instruction
hasFollower = true;
endOfBB = true;
}
}
break;
default:
if (opcode >= 26 && opcode <= 45)
throw new IllegalStateException("instruction variants not expected here");
break;
}
if (endOfBB) break;
}
endPos = pos;
if (hasFollower && (pos + 1) < flow.instructions.size()) {
// add the following basic block as a successor
LabelNode l = flow.getOrCreateLabelAtPos(pos + 1);
bb = flow.getOrCreateBasicBlock(l);
addFollower(bb);
}
return pos;
}
void addFollower(BasicBlock bb) {
this.follower = bb;
addSuccessor(bb);
}
void addSuccessor(BasicBlock bb) {
if (!successors.contains(bb)) {
this.successors.add(bb);
bb.numPredecessors++;
}
}
public Usage getVarUsage() {
return usage;
}
int lastInstruction() {
AbstractInsnNode ainode = getInstruction(endPos);
return ainode.getOpcode();
}
/*
* Blocks connected by an edge are candidates for coalescing if: <dl> <li>
* There is a single edge between the two and neither has any other edges.
* </li>
*
* <li> The edge connecting the two is not because of a GOTO. We only want
* those where one block falls into the other. The reason is that each block
* marks out a *contiguous* range of instructions. Most compilers would have
* gotten rid of this unnecessary jump anyway. </li>
*
* <li> The successor block doesn't begin with a method call that we are
* interested in (like pausable methods). This is a boundary we are
* interested in maintaining in subsequent processing. </li>
*
* </dl>
*/
void coalesceTrivialFollowers() {
while (true) {
if (successors.size() == 1) {
BasicBlock succ = successors.get(0);
if (succ.numPredecessors == 1 && lastInstruction() != GOTO && lastInstruction() != JSR
&& !succ.isPausable()) {
// successor can be merged
// absorb succesors and usage mask
this.successors = succ.successors;
this.follower = succ.follower;
this.usage.absorb(succ.usage);
this.endPos = succ.endPos;
succ.setFlag(COALESCED);
// mark succ so it doesn't get visited. This block's merk remains 0. We'll let the outer driver
// loop to
// revisit this block and its new successors
continue;
}
}
break;
}
}
// Made public for testing purposes
public void setFlag(int bitFlag) {
flags |= bitFlag;
}
public void unsetFlag(int bitFlag) {
flags &= ~bitFlag;
}
public boolean hasFlag(int bitFlag) {
return (flags & bitFlag) != 0;
}
public int compareTo(BasicBlock o) {
if (this.id == o.id) {
assert this == o; // Just in case we have mistakenly assigned the
// same id to different BBs
return 0;
}
return this.id < o.id ? -1 : +1;
}
/*
* This is the main workhorse of the flow analysis phase, translating each
* instruction's effects on the stack and local variables. Unlike the
* verifier which tracks the flow of types, this method tracks values,
* which allows us to track types as well as the flow of constant values
* and set the stage for SSA-style optimizations.
*/
void interpret() {
Value v, v1, v2, v3, v4;
Frame frame = startFrame.dup();
if (isCatchHandler()) {
// When an exception is thrown, the stack is cleared
// and the thrown exception is pushed into the stack
frame.clearStack();
frame.push(Value.make(startPos, caughtExceptionType));
} else if (hasFlag(IS_SUBROUTINE)) {
// The target of a JSR instruction has a JVM-internal
// return address which we model with a type of its
// own
frame.push(Value.make(startPos, D_RETURN_ADDRESS));
}
String componentType = null;
@SuppressWarnings("unused")
boolean canThrowException = false;
boolean propagateFrame = true;
int i = 0;
try {
for (i = startPos; i <= endPos; i++) {
AbstractInsnNode ain = getInstruction(i);
int opcode = ain.getOpcode();
int val, var;
switch (opcode) {
case -1: // linenumbernode, framenode, etc.
continue;
case NOP:
break;
case ACONST_NULL:
frame.push(Value.make(i, D_NULL));
break;
case ICONST_M1:
case ICONST_0:
case ICONST_1:
case ICONST_2:
case ICONST_3:
case ICONST_4:
case ICONST_5:
frame.push(Value.make(i, D_INT, new Integer(opcode
- ICONST_0)));
break;
case LCONST_0:
case LCONST_1:
frame.push(Value.make(i, D_LONG, new Long(opcode - LCONST_0)));
break;
case ILOAD:
case LLOAD:
case FLOAD:
case DLOAD:
case ALOAD:
var = ((VarInsnNode)ain).var;
v = frame.getLocal(var, opcode);
frame.push(v);
break;
case FCONST_0:
case FCONST_1:
case FCONST_2:
frame.push(Value.make(i, D_FLOAT, new Float(opcode
- FCONST_0)));
break;
case DCONST_0:
case DCONST_1:
frame.push(Value.make(i, D_DOUBLE, new Double(opcode
- DCONST_0)));
break;
case BIPUSH:
val = ((IntInsnNode) ain).operand;
frame.push(Value.make(i, D_BYTE, new Integer(val)));
break;
case SIPUSH:
val = ((IntInsnNode) ain).operand;
frame.push(Value.make(i, D_SHORT, new Integer(val)));
break;
case LDC:
Object cval = ((LdcInsnNode) ain).cst;
frame.push(Value.make(i, TypeDesc.getTypeDesc(cval), cval));
break;
case IALOAD:
case LALOAD:
case FALOAD:
case DALOAD:
case AALOAD:
case BALOAD:
case CALOAD:
case SALOAD:
canThrowException = true;
frame.popWord(); // pop index
v = frame.popWord(); // array ref
frame.push(Value.make(i, TypeDesc.getComponentType(v.getTypeDesc()))); // push
// component
// of
// array
break;
case ISTORE:
case LSTORE:
case FSTORE:
case DSTORE:
case ASTORE:
v1 = frame.pop();
var = ((VarInsnNode) ain).var;
frame.setLocal(var, v1);
break;
case IASTORE:
case LASTORE:
case FASTORE:
case DASTORE:
case AASTORE:
case BASTORE:
case CASTORE:
case SASTORE:
canThrowException = true;
frame.popn(3);
break;
case POP:
frame.popWord();
break;
case POP2:
if (frame.pop().isCategory1()) {
frame.popWord();
}
break;
case DUP:
// ... w => ... w w
v = frame.popWord();
frame.push(v);
frame.push(v);
break;
case DUP_X1:
// Insert top word beneath the next word
// .. w2 w1 => .. w1 w2 w1
v1 = frame.popWord();
v2 = frame.popWord();
frame.push(v1);
frame.push(v2);
frame.push(v1);
break;
case DUP_X2:
// Insert top word beneath the next two words (or dword)
v1 = frame.popWord();
v2 = frame.pop();
if (v2.isCategory1()) {
v3 = frame.pop();
if (v3.isCategory1()) {
// w3,w2,w1 => w1,w3,w2,w1
frame.push(v1);
frame.push(v3);
frame.push(v2);
frame.push(v1);
break;
}
} else {
// dw2,w1 => w1,dw2,w1
frame.push(v1);
frame.push(v2);
frame.push(v1);
break;
}
throw new InternalError("Illegal use of DUP_X2");
case DUP2:
// duplicate top two words (or dword)
v1 = frame.pop();
if (v1.isCategory1()) {
v2 = frame.pop();
if (v2.isCategory1()) {
// w2,w1 => w2,w1,w2,w1
frame.push(v2);
frame.push(v1);
frame.push(v2);
frame.push(v1);
break;
}
} else {
// dw1 => dw1,dw1
frame.push(v1);
frame.push(v1);
break;
}
throw new InternalError("Illegal use of DUP2");
case DUP2_X1:
// insert two words (or dword) beneath next word
v1 = frame.pop();
if (v1.isCategory1()) {
v2 = frame.pop();
if (v2.isCategory1()) {
v3 = frame.popWord();
// w3,w2,w1 => w2,w1,w3,w2,w1
frame.push(v2);
frame.push(v1);
frame.push(v3);
frame.push(v2);
frame.push(v1);
break;
}
} else { // TypeDesc.isDoubleWord(t1)
// w2,dw1 => dw1,w2,dw1
v2 = frame.popWord();
frame.push(v1);
frame.push(v2);
frame.push(v1);
break;
}
throw new InternalError("Illegal use of DUP2_X1");
case DUP2_X2:
// insert two words (or dword) beneath next two words (or
// dword)
v1 = frame.pop();
if (v1.isCategory1()) {
v2 = frame.pop();
if (v2.isCategory1()) {
v3 = frame.pop();
if (v3.isCategory1()) {
v4 = frame.pop();
if (v4.isCategory1()) {
// w4,w3,w2,w1 => w2,w1,w4,w3,w2,w1
frame.push(v2);
frame.push(v1);
frame.push(v4);
frame.push(v3);
frame.push(v2);
frame.push(v1);
break;
}
} else { // TypeDesc.isDoubleWord(t3)
// dw3,w2,w1 => w2,w1,dw3,w2,w1
frame.push(v2);
frame.push(v1);
frame.push(v3);
frame.push(v2);
frame.push(v1);
break;
}
}
} else { // TypeDesc.isDoubleWord(t1)
v2 = frame.pop();
if (v2.isCategory1()) {
v3 = frame.pop();
if (v3.isCategory1()) {
// w3,w2,dw1 => dw1,w3,w2,dw1
frame.push(v1);
frame.push(v3);
frame.push(v2);
frame.push(v1);
break;
}
} else {
// dw2,dw1 => dw1,dw2,dw1
frame.push(v1);
frame.push(v2);
frame.push(v1);
break;
}
}
throw new InternalError("Illegal use of DUP2_X2");
case SWAP:
// w2, w1 => w1, w2
v1 = frame.popWord();
v2 = frame.popWord();
frame.push(v1);
frame.push(v2);
break;
case IDIV:
case IREM:
case LDIV:
case LREM:
frame.pop(); // See next case
canThrowException = true;
break;
case IADD:
case LADD:
case FADD:
case DADD:
case ISUB:
case LSUB:
case FSUB:
case DSUB:
case IMUL:
case LMUL:
case FMUL:
case DMUL:
case FDIV:
case DDIV:
case FREM:
case DREM:
case ISHL:
case LSHL:
case ISHR:
case LSHR:
case IUSHR:
case LUSHR:
case IAND:
case LAND:
case IOR:
case LOR:
case IXOR:
case LXOR:
// Binary op.
frame.pop();
v = frame.pop();
// The result is always the same type as the first arg
frame.push(Value.make(i, v.getTypeDesc()));
break;
case LCMP:
case FCMPL:
case FCMPG:
case DCMPL:
case DCMPG:
frame.popn(2);
frame.push(Value.make(i, D_INT));
break;
case INEG:
case LNEG:
case FNEG:
case DNEG:
v = frame.pop();
frame.push(Value.make(i, v.getTypeDesc()));
break;
case IINC:
var = ((IincInsnNode) ain).var;
frame.setLocal(var, Value.make(i, D_INT));
break;
case I2L:
case F2L:
case D2L:
frame.pop();
frame.push(Value.make(i, D_LONG));
break;
case I2D:
case L2D:
case F2D:
frame.pop();
frame.push(Value.make(i, D_DOUBLE));
break;
case I2F:
case L2F:
case D2F:
frame.pop();
frame.push(Value.make(i, D_FLOAT));
break;
case L2I:
case F2I:
case D2I:
frame.pop();
frame.push(Value.make(i, D_INT));
break;
case I2B:
frame.popWord();
frame.push(Value.make(i, D_BOOLEAN));
break;
case I2C:
frame.popWord();
frame.push(Value.make(i, D_CHAR));
break;
case I2S:
frame.popWord();
frame.push(Value.make(i, D_SHORT));
break;
case IFEQ:
case IFNE:
case IFLT:
case IFGE:
case IFGT:
case IFLE:
case IFNULL:
case IFNONNULL:
frame.popWord();
break;
case IF_ICMPEQ:
case IF_ICMPNE:
case IF_ICMPLT:
case IF_ICMPGE:
case IF_ICMPGT:
case IF_ICMPLE:
case IF_ACMPEQ:
case IF_ACMPNE:
frame.popn(2);
break;
case GOTO:
case JSR: // note: the targetBB pushes the return address
// itself
// because it is marked with isSubroutine
case RET:
break;
case TABLESWITCH:
case LOOKUPSWITCH:
frame.pop();
break;
case IRETURN:
case LRETURN:
case FRETURN:
case DRETURN:
case ARETURN:
case RETURN:
canThrowException = true;
if (opcode != RETURN) {
frame.pop();
}
if (frame.stacklen != 0) {
throw new InternalError("stack non null at method return");
}
break;
case GETSTATIC:
canThrowException = true;
v = Value.make(i, TypeDesc.getInterned(((FieldInsnNode) ain).desc));
frame.push(v);
break;
case PUTSTATIC:
canThrowException = true;
frame.pop();
break;
case GETFIELD:
canThrowException = true;
v1 = frame.pop();
v = Value.make(i, TypeDesc.getInterned(((FieldInsnNode) ain).desc));
//if (TypeDesc.isRefType(v.getTypeDesc())) {
// System.out.println("GETFIELD " + ((FieldInsnNode)ain).name + ": " + v + "---->" + v1);
//}
frame.push(v);
break;
case PUTFIELD:
canThrowException = true;
v1 = frame.pop();
v = frame.pop();
//if (TypeDesc.isRefType(v.getTypeDesc())) {
// System.out.println("PUTFIELD " + ((FieldInsnNode)ain).name + ": " + v + " ----> " + v1);
//}
break;
case INVOKEVIRTUAL:
case INVOKESPECIAL:
case INVOKESTATIC:
case INVOKEINTERFACE:
// pop args, push return value
MethodInsnNode min = ((MethodInsnNode) ain);
String desc = min.desc;
if (flow.isPausableMethodInsn(min) && frame.numMonitorsActive > 0) {
throw new KilimException("Error: Can not call pausable nethods from within a synchronized block\n" +
"Caller: " + this.flow.classFlow.name.replace('/', '.') + "." + this.flow.name + this.flow.desc +
"\nCallee: " + ((MethodInsnNode)ain).name);
}
canThrowException = true;
frame.popn(TypeDesc.getNumArgumentTypes(desc));
if (opcode != INVOKESTATIC) {
v = frame.pop(); // "this" ref
//assert checkReceiverType(v, min) : "Method " + flow.name + " calls " + min.name + " on a receiver with incompatible type " + v.getTypeDesc() ;
}
desc = TypeDesc.getReturnTypeDesc(desc);
if (desc != D_VOID) {
frame.push(Value.make(i, desc));
}
break;
case NEW:
canThrowException = true;
v = Value.make(i, TypeDesc.getInterned(((TypeInsnNode) ain).desc));
frame.push(v);
break;
case NEWARRAY:
canThrowException = true;
frame.popWord();
int atype = ((IntInsnNode) ain).operand;
String t;
switch (atype) {
case T_BOOLEAN:
t = D_ARRAY_BOOLEAN;
break;
case T_CHAR:
t = D_ARRAY_CHAR;
break;
case T_FLOAT:
t = D_ARRAY_FLOAT;
break;
case T_DOUBLE:
t = D_ARRAY_DOUBLE;
break;
case T_BYTE:
t = D_ARRAY_BYTE;
break;
case T_SHORT:
t = D_ARRAY_SHORT;
break;
case T_INT:
t = D_ARRAY_INT;
break;
case T_LONG:
t = D_ARRAY_LONG;
break;
default:
throw new InternalError("Illegal argument to NEWARRAY: "
+ atype);
}
frame.push(Value.make(i, t));
break;
case ANEWARRAY:
canThrowException = true;
frame.popWord();
componentType = TypeDesc.getInterned(((TypeInsnNode) ain).desc);
v = Value.make(i, TypeDesc.getInterned("[" + componentType));
frame.push(v);
break;
case ARRAYLENGTH:
canThrowException = true;
frame.popWord();
frame.push(Value.make(i, D_INT));
break;
case ATHROW:
canThrowException = true;
frame.pop();
propagateFrame = false;
break;
case CHECKCAST:
canThrowException = true;
frame.pop();
v = Value.make(i, TypeDesc.getInterned(((TypeInsnNode) ain).desc));
frame.push(v);
break;
case INSTANCEOF:
canThrowException = true;
frame.pop();
frame.push(Value.make(i, D_INT));
break;
case MONITORENTER:
case MONITOREXIT:
if (opcode == MONITORENTER) {
frame.numMonitorsActive++;
} else {
frame.numMonitorsActive--;
}
canThrowException = true;
frame.pop();
canThrowException = true;
break;
case MULTIANEWARRAY:
MultiANewArrayInsnNode minode = (MultiANewArrayInsnNode) ain;
int dims = minode.dims;
frame.popn(dims);
componentType = TypeDesc.getInterned(minode.desc);
StringBuffer sb = new StringBuffer(componentType.length()
+ dims);
for (int j = 0; j < dims; j++)
sb.append('[');
sb.append(componentType);
v = Value.make(i, TypeDesc.getInterned(sb.toString()));
frame.push(v);
break;
default:
assert false : "Unexpected opcode: " + ain.getOpcode();
}
}
i = -1; // reset for assertion catch block below
if (propagateFrame) {
mergeSuccessors(frame);
}
if (handlers != null) {
for (Handler handler : handlers) {
handler.catchBB.merge(frame, /* localsOnly= */true); // merge
// only
// locals
}
canThrowException = false;
}
} catch (AssertionError ae) {
System.err.println("**** Assertion Error analyzing " + flow.classFlow.name + "." + flow.name);
System.err.println("Basic block " + this);
System.err.println("i = " + i);
System.err.println("Frame: " + frame);
throw ae;
}
}
/*
private boolean checkReceiverType(Value v, MethodInsnNode min) {
String t = v.getTypeDesc();
if (t == D_NULL) {
return true;
}
t = TypeDesc.getInternalName(t);
return detector().getPausableStatus(t, min.name, min.desc) != Detector.METHOD_NOT_FOUND;
}
*/
public boolean isCatchHandler() {
return caughtExceptionType != null;
}
void mergeSuccessors(Frame frame) {
for (BasicBlock s : successors) {
s.merge(frame, false);
}
}
/**
* @param inframe
* @param localsOnly
*/
void merge(Frame inframe, boolean localsOnly) {
boolean enqueue = true;
if (startFrame == null) {
startFrame = inframe.dup();
} else {
Frame ret;
// Absorb only those local vars dictacted by usage.in.
ret = startFrame.merge(inframe, localsOnly, usage);
if (ret == startFrame) { // no change
enqueue = false;
} else {
startFrame = ret;
}
}
if (enqueue) {
flow.enqueue(this);
}
}
public void chooseCatchHandlers(ArrayList<Handler> handlerList) {
for (Handler h : handlerList) {
if (this == h.catchBB) {
// This bb is one of the catch handlers
caughtExceptionType = TypeDesc.getInterned((h.type == null ? THROWABLE_CLASS
: h.type));
} else {
Range ri = Range.intersect(startPos, endPos, h.from, h.to);
if (ri != null) {
handlers.add(new Handler(ri.from, ri.to, h.type, h.catchBB));
}
}
}
}
public AbstractInsnNode getInstruction(int pos) {
return (AbstractInsnNode) flow.instructions.get(pos);
}
public boolean flowVarUsage() {
// for live var analysis, treat catch handlers as successors too.
if (succUsage == null) {
succUsage = new ArrayList<Usage>(successors.size()
+ handlers.size());
for (BasicBlock succ : successors) {
succUsage.add(succ.usage);
}
for (Handler h : handlers) {
succUsage.add(h.catchBB.usage);
}
}
return usage.evalLiveIn(succUsage);
}
/**
* This basic block's last instruction is JSR. This method initiates a
* subgraph traversal to identify the called subroutine's boundaries and to
* make all encountered RET instructions point back to this BB's follower,
* in essence turning it to a goto. The reason for not actually turning it
* into a GOTO is that if we don't find any pausable methods in a
* subroutine, then during code generation we'll simply use the original
* code. The duplication is still required for flow analysis.
*
* The VM spec is fuzzy on what constitutes the boundaries of a subroutine.
* We consider the following situations invalid, even though the verifier is
* ok with it: (a) looping back to itself (b) encountering xRETURN in a subroutine
*
* inline() traverses the graph creating copies of BasicBlocks and labels
* and keeps a mapping between the old and the new. In the second round, it
* copies instructions translating any that have labels (branch and switch
* instructions).
*
* @return mapping of orig basic blocks to new.
*
*/
ArrayList<BasicBlock> inline() throws KilimException {
HashMap<BasicBlock, BasicBlock> bbCopyMap = null;
HashMap<LabelNode, LabelNode> labelCopyMap = null;
BasicBlock targetBB = successors.get(0);
LabelNode returnToLabel = flow.getOrCreateLabelAtPos(endPos+1);
BasicBlock returnToBB = flow.getOrCreateBasicBlock(returnToLabel);
boolean isPausableSub = targetBB.hasFlag(PAUSABLE_SUB);
if (!targetBB.hasFlag(SUBROUTINE_CLAIMED)) {
// This JSR call gets to claim the subroutine's blocks, so no
// copying required. If another JSR wants to point to the same
// subroutine, it'll copy BBs on demand)
targetBB.setFlag(SUBROUTINE_CLAIMED);
// Tell the RET blocks about the returnTo address and we are done.
for (BasicBlock b : targetBB.getSubBlocks()) {
if (b.lastInstruction() == RET) {
assert b.successors.size() == 0 : this.toString();
b.addSuccessor(returnToBB);
}
}
return null;
}
bbCopyMap = new HashMap<BasicBlock, BasicBlock>(10);
labelCopyMap = new HashMap<LabelNode, LabelNode>(10);
successors.clear();
// first pass
targetBB.dupBBAndLabels(isPausableSub, bbCopyMap, labelCopyMap, returnToBB);
addSuccessor(bbCopyMap.get(targetBB));
// second pass
return dupCopyContents(isPausableSub, targetBB, returnToBB, bbCopyMap, labelCopyMap);
}
void dupBBAndLabels(boolean deepCopy,
HashMap<BasicBlock, BasicBlock> bbCopyMap,
HashMap<LabelNode, LabelNode> labelCopyMap, BasicBlock returnToBB)
throws KilimException {
for (BasicBlock orig : getSubBlocks()) {
BasicBlock dup = new BasicBlock(flow, orig.startLabel);
bbCopyMap.put(orig, dup);
if (deepCopy) {
// copy labels for each instruction. This copy will be used
// in dupCopyContents
for (int i = orig.startPos; i <= orig.endPos; i++) {
LabelNode origLabel = flow.getLabelAt(i);
if (origLabel != null) {
LabelNode l = labelCopyMap.put(origLabel, new LabelNode());
assert l == null;
}
}
// dup.startLabel reset later in dupCopyContents
}
}
}
static ArrayList<BasicBlock> dupCopyContents(boolean deepCopy,
BasicBlock targetBB, BasicBlock returnToBB,
HashMap<BasicBlock, BasicBlock> bbCopyMap,
HashMap<LabelNode, LabelNode> labelCopyMap) throws KilimException {
ArrayList<BasicBlock> newBBs = new ArrayList<BasicBlock>(targetBB.getSubBlocks().size());
for (BasicBlock orig : targetBB.getSubBlocks()) {
BasicBlock dup = bbCopyMap.get(orig);
dup.flags = orig.flags;
dup.caughtExceptionType = orig.caughtExceptionType;
dup.startPos = orig.startPos;
dup.endPos = orig.endPos;
dup.flow = orig.flow;
dup.numPredecessors = orig.numPredecessors;
dup.startFrame = null;
dup.usage = orig.usage.copy();
dup.handlers = orig.handlers;
if (orig.follower != null) {
dup.follower = bbCopyMap.get(orig.follower);
if (dup.follower == null) {
assert dup.lastInstruction() == RET;
}
}
dup.successors = new ArrayList<BasicBlock>(orig.successors.size());
if (orig.lastInstruction() == RET) {
dup.addSuccessor(returnToBB);
} else {
for (BasicBlock s : orig.successors) {
BasicBlock b = bbCopyMap.get(s);
dup.addSuccessor(b);
}
}
if (deepCopy) {
InsnList extraInsns = new InsnList();
MethodFlow flow = targetBB.flow;
InsnList instructions = flow.instructions;
// copy instructions
dup.startLabel = labelCopyMap.get(orig.startLabel);
dup.startPos = instructions.size();
dup.endPos = dup.startPos + (orig.endPos - orig.startPos);
// Note: last instruction (@endPos) isn't copied in the loop.
// If it has labels, a new instruction is generated; either
// way the last instruction is appended separately.
int i;
int newPos = instructions.size();
int end = orig.endPos;
// create new labels and instructions
for (i = orig.startPos; i <= end; i++, newPos++) {
LabelNode l = flow.getLabelAt(i);
if (l != null) {
l = labelCopyMap.get(l);
assert l != null;
flow.setLabel(newPos, l);
}
extraInsns.add(instructions.get(i).clone(labelCopyMap));
}
// new handlers
dup.handlers = new ArrayList<Handler>(orig.handlers.size());
if (orig.handlers.size() > 0) {
for (Handler oh : orig.handlers) {
Handler h = new Handler(dup.startPos
+ (oh.from - orig.startPos), dup.endPos
+ (oh.to - orig.endPos), oh.type, oh.catchBB);
dup.handlers.add(h);
}
}
instructions.add(extraInsns);
}
newBBs.add(dup);
}
return newBBs;
}
public BasicBlock getJSRTarget() {
return lastInstruction() == JSR ? successors.get(0) : null;
}
/*
* Invoked on the subroutine entry point's BB. Returns all the BBs
* linked to it.
*/
public ArrayList<BasicBlock> getSubBlocks() throws KilimException {
if (subBlocks == null) {
if (!hasFlag(IS_SUBROUTINE))
return null;
subBlocks = new ArrayList<BasicBlock>(10);
Stack<BasicBlock> stack = new Stack<BasicBlock>();
this.setFlag(SUB_BLOCK);
stack.add(this);
while (!stack.isEmpty()) {
BasicBlock b = stack.pop();
subBlocks.add(b);
if (b.lastInstruction() == JSR) {
// add the following block, but not its target
BasicBlock follower = b.getFollowingBlock();
if (!follower.hasFlag(SUB_BLOCK)) {
follower.setFlag(SUB_BLOCK);
stack.push(follower);
}
continue;
}
for (BasicBlock succ : b.successors) {
if (succ == this) {
throw new KilimException("JSRs looping back to themselves are not supported");
}
if (!succ.hasFlag(SUB_BLOCK)) {
succ.setFlag(SUB_BLOCK);
stack.push(succ);
}
}
}
Collections.sort(subBlocks);
}
return subBlocks;
}
BasicBlock getFollowingBlock() {
if (follower != null) return follower;
// otherwise we'll return the next block anyway. This is used
// to get the block following a JSR instruction, even though
// it is not a follower in the control flow sense.
LabelNode l = flow.getLabelAt(endPos+1);
assert l != null : "No block follows this block: " + this;
return flow.getBasicBlock(l);
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer(200);
sb.append("\n========== BB #").append(id).append("[").append(System.identityHashCode(this)).append("]\n");
sb.append("method: ").append(this.flow.name).append(this.flow.desc).append("\n");
sb.append("start = ").append(startPos).append(",end = ").append(endPos).append('\n').append("Successors:");
if (successors.isEmpty())
sb.append(" None");
else {
for (int i = 0; i < successors.size(); i++) {
BasicBlock succ = successors.get(i);
sb.append(" ").append(succ.id).append("[").append(System.identityHashCode(succ)).append("]");
}
}
sb.append("\nHandlers:");
if (handlers.isEmpty())
sb.append(" None");
else {
for (int i = 0; i < handlers.size(); i++) {
sb.append(" ").append(handlers.get(i).catchBB.id);
}
}
sb.append("\nStart frame:\n").append(startFrame);
sb.append("\nUsage: ").append(usage);
return sb.toString();
}
public boolean isPausable() {
return hasFlag(PAUSABLE);
}
void setId(int aid) {
id = aid;
}
/*
* If any BB belonging to a subroutine makes a pausable
* block, it taints all the blocks within the subroutine's
* purview as PAUSABLE_SUB
*/
void checkPausableJSR() throws KilimException {
BasicBlock sub = getJSRTarget();
boolean isPausableJSR = false;
if (sub != null) {
ArrayList<BasicBlock> subBlocks = sub.getSubBlocks();
for (BasicBlock b: subBlocks) {
if (b.hasFlag(PAUSABLE)) {
isPausableJSR = true;
break;
}
}
if (isPausableJSR) {
for (BasicBlock b: subBlocks) {
b.setFlag(PAUSABLE_SUB);
}
}
}
}
void changeJSR_RET_toGOTOs() throws KilimException {
int lastInsn = getInstruction(endPos).getOpcode();
if (lastInsn == JSR) {
BasicBlock targetBB = successors.get(0);
if (!targetBB.hasFlag(PAUSABLE_SUB)) return;
changeLastInsnToGOTO(targetBB.startLabel);
successors.clear();
successors.add(targetBB);
// change the first ASTORE instruction in targetBB to a NOP
assert targetBB.getInstruction(targetBB.startPos).getOpcode() == ASTORE;
targetBB.setInstruction(targetBB.startPos, new NopInsn());
targetBB.unsetFlag(IS_SUBROUTINE);
} else if (lastInsn == RET && hasFlag(PAUSABLE_SUB)) {
changeLastInsnToGOTO(successors.get(0).startLabel);
}
}
void setInstruction(int pos, AbstractInsnNode insn) {
flow.instructions.set(getInstruction(pos), insn);
}
void changeLastInsnToGOTO(LabelNode label) {
setInstruction(endPos, new JumpInsnNode(GOTO, label));
}
public boolean isGetCurrentTask() {
AbstractInsnNode ain = getInstruction(startPos);
if (ain.getOpcode() == INVOKESTATIC) {
MethodInsnNode min = (MethodInsnNode)ain;
return min.owner.equals(TASK_CLASS) && min.name.equals("getCurrentTask");
}
return false;
}
boolean isInitialized() {
return startPos >= 0 && endPos >=0;
}
}
class BBComparator implements Comparator<BasicBlock> {
public int compare(BasicBlock o1, BasicBlock o2) {
if (o1.id == o2.id) {
return 0;
}
return o1.id < o2.id ? -1 : +1;
}
}