kilim.analysis.BasicBlock Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kilim Show documentation
Show all versions of kilim Show documentation
Coroutines, continuations, fibers, actors and message passing for the JVM
/* 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.InvokeDynamicInsnNode;
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.
*
*
*
* 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.
*
* All catch handlers that intersect a basic block are treated as
* successors to the block, for the purposes of liveness analysis.
*
* 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)
*
*/
public class BasicBlock implements Comparable {
/**
* 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 successors = new ArrayList(3);
public ArrayList handlers = new ArrayList(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;
/** the block has already been visited during the born calculation */
boolean visited;
/**
* A cached version of all sucessors' usage, successors being catch handlers
* and real successors.
*/
ArrayList succUsage;
ArrayList handUsage;
/**
* 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 subBlocks;
public BasicBlock(MethodFlow aflow, LabelNode aStartLabel) {
flow = aflow;
startLabel = aStartLabel;
usage = new Usage(aflow.maxLocals);
successors = new ArrayList(2);
}
/**
* 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 nextCatch = flow.mapHandler(pos+1);
int size = flow.instructions.size();
for (; pos < size; pos++) {
if (pos > startPos && (pos==nextCatch || 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 otherLabels;
if (opcode == TABLESWITCH) {
defaultLabel = ((TableSwitchInsnNode) ain).dflt;
otherLabels = ((TableSwitchInsnNode) ain).labels;
} else {
defaultLabel = ((LookupSwitchInsnNode) ain).dflt;
otherLabels = ((LookupSwitchInsnNode) ain).labels;
}
for (Iterator 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:
// Note that the case of INVOKEDYNAMIC does not need to be handled
// because it is merely a placeholder for an adaptor that converts captured/dynamic
// arguments to a functional interface. This is never going to be pausable.
if (flow.isPausableMethodInsn((MethodInsnNode) ain)) {
LabelNode il = flow.getOrCreateLabelAtPos(pos);
if (pos == startPos) {
setFlag(PAUSABLE);
endOfBB = true;
} 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:
* There is a single edge between the two and neither has any other edges.
*
*
* 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.
*
* 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.
*
*
*/
void coalesceTrivialFollowers() {
while (true) {
if (successors.size() == 1) {
BasicBlock succ = successors.get(0);
// fixme:generality - should the endPos be checked too
// (in java, seems like there should always be a goto, so not needed)
int nextCatch = flow.mapHandler(succ.startPos);
boolean isTry = nextCatch==succ.startPos;
if (succ.numPredecessors == 1 && lastInstruction() != GOTO && lastInstruction() != JSR
&& !succ.isPausable() && !isPausable() && !isTry) {
// 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 INVOKEDYNAMIC: {
InvokeDynamicInsnNode indy = (InvokeDynamicInsnNode)ain;
String desc = indy.desc;
frame.popn(TypeDesc.getNumArgumentTypes(desc));
desc = TypeDesc.getReturnTypeDesc(desc);
assert (desc != D_VOID) : "InvokeDynamic return value should be a functional interface";
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(flow.detector, inframe, localsOnly, usage);
if (ret == startFrame) { // no change
enqueue = false;
} else {
startFrame = ret;
}
}
if (enqueue) {
flow.enqueue(this);
}
}
public void chooseCatchHandlers(ArrayList 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);
}
/** pretty print successor and catch BB ids */
String printGeniology() {
String ts = "";
for (BasicBlock succ : successors)
ts += String.format(" %4d",succ.id);
String th = "";
for (Handler h : handlers)
th += String.format(" %4d",h.catchBB.id);
return String.format("%4d:%-20s...%-20s",id,ts,th);
}
/** calculate the liveness usage by evolution (assume born usage has already been calculated) */
public boolean flowVarUsage() {
// assert(succBlocks != null);
if (succUsage == null) {
succUsage = new ArrayList(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,handlers);
}
/**
* 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 inline() throws KilimException {
HashMap bbCopyMap = null;
HashMap 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(10);
labelCopyMap = new HashMap(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 bbCopyMap,
HashMap 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 dupCopyContents(boolean deepCopy,
BasicBlock targetBB, BasicBlock returnToBB,
HashMap bbCopyMap,
HashMap labelCopyMap) throws KilimException {
ArrayList newBBs = new ArrayList(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(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(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 getSubBlocks() throws KilimException {
if (subBlocks == null) {
if (!hasFlag(IS_SUBROUTINE))
return null;
subBlocks = new ArrayList(10);
Stack stack = new Stack();
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 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 {
public int compare(BasicBlock o1, BasicBlock o2) {
if (o1.id == o2.id) {
return 0;
}
return o1.id < o2.id ? -1 : +1;
}
}