上一篇分析了可织入判断的代码,本篇继续分析织入部分的构造/合并BasicBlock。
首先看下分析方法kilim.analysis.MethodFlow.analyze()包含的功能:
//织入逻辑 public void analyze() throws KilimException { buildBasicBlocks();//构造BasicBlock,异常代码怎么构建成块的? if (basicBlocks.size() == 0) return; consolidateBasicBlocks();//这里会合并一些在逻辑上不需要独立开来的块 assignCatchHandlers();//把tryCatchBlocks块构造成Handler,然后根据handler的块的起始位置和块的起始位置,给块分配handler,即tryCatch块会catch这个bb的某个类型的异常;tryCatchBlock开始pos会跟代码bb的开始位置重合,会在bb的start和end pos之间么?consolidate合并块导致? inlineSubroutines();//这里只是把小于1.5编译级别所产生的jsr指令做了处理;finally块中有pausable则会被拷贝一份;finally块中有pausable则会把jsr/ret指令都替换为goto指令,但是并没有像1.5及以后编译级别那样,拷贝一份代码try/catch代码块编译后的指令块中 doLiveVarAnalysis();//变量活动性分析 dataFlow();//数据流分析 this.labelToBBMap = null; // we don't need this mapping anymore }
来看buildBasicBlock方法及调用的相关方法的实现逻辑:
//构造BasicBlock void buildBasicBlocks() { // preparatory phase int numInstructions = instructions.size(); basicBlocks = new BBList();//Basic Block 列表 // Note: i modified within the loop for (int i = 0; i < numInstructions; i++) { Label l = getOrCreateLabelAtPos(i);//在当前指令位置创建一个Label,这里i并不是连续的,即并非每个位置都会创建一条指令 BasicBlock bb = getOrCreateBasicBlock(l);//为每个Label创建一个BasicBlock i = bb.initialize(i); // i now points to the last instruction in bb.在bb.initialize(i)里,i会增加的 basicBlocks.add(bb);//放到bb列表 } } Label getOrCreateLabelAtPos(int pos) { Label ret = null; if (pos < posToLabelMap.size()) { ret = posToLabelMap.get(pos);//这是一个List,不是一个Map的 } if (ret == null) { ret = new Label(); setLabel(pos, ret);//为位置pos创建一个Label } return ret; } void setLabel(int pos, Label l) { //在posToLabelMap后边添加pos - posToLabelMap.size() + 1个null,还是为了做到每条指令都对应一个位置,存储值为null或Label for (int i = pos - posToLabelMap.size() + 1; i >= 0; i--) { //pad with nulls ala perl posToLabelMap.add(null); } assert posToLabelMap.get(pos) == null; posToLabelMap.set(pos, l);//把pos位置上放一个Label labelToPosMap.put(l, pos);//Label和位置的映射关系 }
下边着重分析下BasicBlock的initialize,分析前先介绍两个概念,BasicBlock是一个指令集合,这些指令集合也叫做一个节点,是必须按照顺序执行的一堆指令,中间不存在跳转。如果按照顺序执行指令,执行当前节点的最后一条指令,与接下来要执行的下一个节点的第一条指令之间是物理相邻的,那么下一个节点就是当前节点的follower。从一个块可以通过跳转或者非跳转指令直接到达的节点,叫做当前节点的后继节点successor,当前节点也叫做后继结点的前驱节点predecessor。
/** * 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: * 读取尽量多的指令,直到下一个目标指令或者下一个跳转指令。第一pass,我们创建尽可能多的bbs,因为这里有很多非目标label(比如调试信息) * 1. A transfer of control instruction must be the last instruction. It * may also be the first (and only) instruction. 控制流的跳转指令必须是BasicBlock的最后一条指令 * 2. A labeled instruction must be the first instruction in a BB. It * may optionally be the last (and only) instruction. 目标指令必须是BasicBlock的第一条指令 * 3. A pausable method is treated like a labeled instruction, and is * given a label if there isn't one already. Constraint 2 applies. Pausable方法被当目标指令 */ @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) {//如果某条指令已经有对应的Label了 pos--;//回退一位 hasFollower = true;//有后继节点 endOfBB = true;//BasicBlock的末尾 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);//load类型指令,把局部变量索引标记为读 break; case ISTORE: case LSTORE: case FSTORE: case DSTORE: case ASTORE: usage.write(((VarInsnNode) ain).var);//store类型指令,把对应局部变量索引标记为写 break; case IINC: int v = ((IincInsnNode)ain).var; usage.read(v); usage.write(v);//iinc 既读又写 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://if类、jsr、goto类型指令 Label l = ((JumpInsnNode) ain).label;//这些指令的目标地址上都创建了label bb = flow.getOrCreateBasicBlock(l);//跳转指令目标地址上创建一个Label if (opcode == JSR) {//jsr指令,标示为IS_SUBROUTINE bb.setFlag(IS_SUBROUTINE); hasFollower = false;//以jsr指令结束的bb没有follower节点 } addSuccessor(bb);//给当前BasicBlock添加后继节点,后继节点的前驱节点数也会加1 if (opcode == GOTO) {//以goto指令结束的bb没有follower节点 hasFollower = false; } endOfBB = true;//bb结束 break; case RET://finally块的返回指令 case IRETURN: case LRETURN: case FRETURN: case DRETURN: case ARETURN: case RETURN: case ATHROW://以return、throw类型指令和抛异常指令结束的bb没有follower,没有successor hasFollower = false; endOfBB = true;//bb结束 break; case TABLESWITCH: case LOOKUPSWITCH://switch指令 Label defaultLabel;//default跳转的目标位置的label List<Label> otherLabels;//非default跳转的labels if (opcode == TABLESWITCH) { defaultLabel = ((TableSwitchInsnNode) ain).dflt; otherLabels = ((TableSwitchInsnNode) ain).labels; } else { defaultLabel = ((LookupSwitchInsnNode) ain).dflt; otherLabels = ((LookupSwitchInsnNode) ain).labels; } //switch指令的默认和非默认跳转目标地址开始的Label都作为当前bb的后继节点 for (Iterator<Label> it = otherLabels.iterator(); it.hasNext();) { l = (Label) 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)) {//调用的Pausable的方法 if (pos == startPos) {//当前块的第一条指令,那么当前块就是一个pausable块 setFlag(PAUSABLE); } else {//否则,当前块结束,为pausable方法指令创建新块和bb l = flow.getOrCreateLabelAtPos(pos); bb = flow.getOrCreateBasicBlock(l); bb.setFlag(PAUSABLE); addSuccessor(bb);//当前bb的后继节点 pos--; // don't consume this instruction hasFollower = true;//有follower endOfBB = true;//块结束 } } break; default: if (opcode >= 26 && opcode <= 45) throw new IllegalStateException("instruction variants not expected here"); break; } if (endOfBB) break; } endPos = pos; //添加follower,当前bb最后一条指令的下一条指令是follower的第一条指令 if (hasFollower && (pos + 1) < flow.instructions.size()) { // add the following basic block as a successor Label l = flow.getOrCreateLabelAtPos(pos + 1); bb = flow.getOrCreateBasicBlock(l); addFollower(bb);//follower也是一个后继节点的 } return pos; }
整理下以上代码,以jsr、goto、return、throw、switch这些类型指令结束的BasicBlock是没有follower的;以if或者pausable方法的方法调用指令结束的BasicBloc是有follower的,以if、jsr、goto、return、throw、switch、pausable方法的方法调用指令的上一条指令结束的bb是会有后继结点。
asm框架分析出来的其他Label:(都包括啥?)
/** * In the first pass (buildBasicBlocks()), we create BBs whenever we * encounter a label. We don't really know until we are done with that * pass whether a label is the target of a branch instruction or it is * there because of an exception handler. See coalesceWithFollowingBlock() * for more detail. 合并bb */ private void consolidateBasicBlocks() { BBList newBBs = new BBList(basicBlocks.size()); int pos = 0; for (BasicBlock bb: basicBlocks) { if (!bb.hasFlag(COALESCED)) { bb.coalesceTrivialFollowers();//合并 // The original bb's followers should have been marked as processed. bb.setId(pos++); newBBs.add(bb); } } basicBlocks = newBBs; assert checkNoBasicBlockLeftBehind();//校验所有bb都正确构造,指令起始位置正确 } /** * 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> * 什么label隔开的bb可以合并? * </dl> */ void coalesceTrivialFollowers() { while (true) { if (successors.size() == 1) {//当前节点的后继结点只有一个 BasicBlock succ = successors.get(0); if (succ.numPredecessors == 1 && lastInstruction() != GOTO && lastInstruction() != JSR && !succ.isPausable()) {//后继节点的前驱节点也只有一个,当前节点的最后一条指令不是goto/jsr,后继节点第一条指令不是pausable方法调用 // successor can be merged // absorb succesors and usage mask this.successors = succ.successors; this.follower = succ.follower; this.usage.absorb(succ.usage);//usage也合并 this.endPos = succ.endPos; succ.setFlag(COALESCED);//后继节点是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; } }