/src/kilim/analysis/MethodFlow.java

http://github.com/kilim/kilim · Java · 660 lines · 500 code · 89 blank · 71 comment · 112 complexity · 47fedae147e25d996bed9d218c3fbb1e MD5 · raw file

  1. /* Copyright (c) 2006, Sriram Srinivasan
  2. *
  3. * You may distribute this software under the terms of the license
  4. * specified in the file "License"
  5. */
  6. package kilim.analysis;
  7. import static kilim.Constants.NOT_PAUSABLE_CLASS;
  8. import static kilim.Constants.PAUSABLE_CLASS;
  9. import static kilim.analysis.BasicBlock.COALESCED;
  10. import static kilim.analysis.BasicBlock.ENQUEUED;
  11. import static kilim.analysis.BasicBlock.INLINE_CHECKED;
  12. import static org.objectweb.asm.Opcodes.ACC_STATIC;
  13. import static org.objectweb.asm.Opcodes.ACC_VOLATILE;
  14. import static org.objectweb.asm.Opcodes.JSR;
  15. import java.util.ArrayList;
  16. import java.util.BitSet;
  17. import java.util.Collections;
  18. import java.util.HashMap;
  19. import java.util.HashSet;
  20. import java.util.LinkedList;
  21. import java.util.List;
  22. import java.util.PriorityQueue;
  23. import java.util.TreeMap;
  24. import kilim.Constants;
  25. import kilim.KilimException;
  26. import kilim.mirrors.Detector;
  27. import org.objectweb.asm.AnnotationVisitor;
  28. import org.objectweb.asm.Handle;
  29. import org.objectweb.asm.Label;
  30. import org.objectweb.asm.MethodVisitor;
  31. import org.objectweb.asm.Opcodes;
  32. import org.objectweb.asm.tree.AbstractInsnNode;
  33. import org.objectweb.asm.tree.AnnotationNode;
  34. import org.objectweb.asm.tree.FrameNode;
  35. import org.objectweb.asm.tree.InsnList;
  36. import org.objectweb.asm.tree.LabelNode;
  37. import org.objectweb.asm.tree.LineNumberNode;
  38. import org.objectweb.asm.tree.MethodInsnNode;
  39. import org.objectweb.asm.tree.MethodNode;
  40. import org.objectweb.asm.tree.TryCatchBlockNode;
  41. /**
  42. * This represents all the basic blocks of a method.
  43. */
  44. public class MethodFlow extends MethodNode {
  45. /**
  46. * The classFlow to which this methodFlow belongs
  47. */
  48. ClassFlow classFlow;
  49. /**
  50. * Maps instructions[i] to LabelNode or null (if no label). Note that
  51. * LabelInsnNodes are not accounted for here because they themselves are not
  52. * labelled.
  53. */
  54. private ArrayList<LabelNode> posToLabelMap;
  55. /**
  56. * Reverse map of posToLabelMap. Maps Labels to index within
  57. * method.instructions.
  58. */
  59. private HashMap<LabelNode, Integer> labelToPosMap;
  60. /**
  61. * Maps labels to BasicBlocks
  62. */
  63. private HashMap<LabelNode, BasicBlock> labelToBBMap;
  64. /**
  65. * The list of basic blocks, in the order in which they occur in the class file.
  66. * Maintaining this order is important, because we'll use it to drive duplication (in case
  67. * of JSRs) and also while writing out the class file.
  68. */
  69. private BBList basicBlocks;
  70. private PriorityQueue<BasicBlock> workset;
  71. private boolean hasPausableAnnotation;
  72. private boolean suppressPausableCheck;
  73. private List<MethodInsnNode> pausableMethods = new LinkedList<MethodInsnNode>();
  74. final Detector detector;
  75. private TreeMap<Integer, LineNumberNode> lineNumberNodes = new TreeMap<Integer, LineNumberNode>();
  76. private HashMap<Integer, FrameNode> frameNodes = new HashMap<Integer, FrameNode>();
  77. private boolean hasPausableInvokeDynamic;
  78. /** copy of handlers provided by asm - null after being assigned to the BBs */
  79. ArrayList<Handler> origHandlers;
  80. public MethodFlow(
  81. ClassFlow classFlow,
  82. final int access,
  83. final String name,
  84. final String desc,
  85. final String signature,
  86. final String[] exceptions,
  87. final Detector detector) {
  88. super(Constants.KILIM_ASM, access, name, desc, signature, exceptions);
  89. this.classFlow = classFlow;
  90. this.detector = detector;
  91. posToLabelMap = new ArrayList<LabelNode>();
  92. labelToPosMap = new HashMap<LabelNode, Integer>();
  93. labelToBBMap = new HashMap<LabelNode, BasicBlock>();
  94. if (exceptions != null && exceptions.length > 0) {
  95. for (String e: exceptions) {
  96. if (e.equals(PAUSABLE_CLASS)) {
  97. hasPausableAnnotation = true;
  98. break;
  99. } else if (e.equals(NOT_PAUSABLE_CLASS)) {
  100. suppressPausableCheck = true;
  101. }
  102. }
  103. }
  104. }
  105. public void restoreNonInstructionNodes() {
  106. InsnList newinsns = new InsnList();
  107. int sz = instructions.size();
  108. for (int i = 0; i < sz; i++) {
  109. LabelNode l = getLabelAt(i);
  110. if (l != null) {
  111. newinsns.add(l);
  112. }
  113. LineNumberNode ln = lineNumberNodes.get(i);
  114. if (ln != null) {
  115. newinsns.add(ln);
  116. }
  117. AbstractInsnNode ain = instructions.get(i);
  118. newinsns.add(ain);
  119. }
  120. LabelNode l = getLabelAt(sz);
  121. if (l != null) {
  122. newinsns.add(l);
  123. }
  124. LineNumberNode ln = lineNumberNodes.get(sz);
  125. if (ln != null) {
  126. newinsns.add(ln);
  127. }
  128. super.instructions = newinsns;
  129. }
  130. public void analyze() throws KilimException {
  131. preAssignCatchHandlers();
  132. buildBasicBlocks();
  133. if (basicBlocks.size() == 0) return;
  134. consolidateBasicBlocks();
  135. assignCatchHandlers();
  136. inlineSubroutines();
  137. doLiveVarAnalysis();
  138. dataFlow();
  139. this.labelToBBMap = null; // we don't need this mapping anymore
  140. }
  141. public void verifyPausables() throws KilimException {
  142. // If we are looking at a woven file, we don't need to verify
  143. // anything
  144. if (classFlow.isWoven || suppressPausableCheck) return;
  145. String methodText = toString(classFlow.getClassName(),this.name,this.desc);
  146. boolean ctor = this.name.endsWith("init>");
  147. // constructors cannot be pausable because they must begin with the super call
  148. // meaning the weaver is unable to inject the preamble
  149. // and a super with side effects would get called multiple times
  150. // refuse to weave them
  151. if (ctor & hasPausableAnnotation)
  152. throw new KilimException("Constructors cannot be declared Pausable: " + methodText + "\n");
  153. if (!hasPausableAnnotation && !pausableMethods.isEmpty()) {
  154. String msg = ctor
  155. ? "Constructor " + methodText + " illegally calls pausable methods:\n"
  156. : methodText + " should be marked pausable. It calls pausable methods\n";
  157. for (MethodInsnNode min: pausableMethods)
  158. msg += toString(min.owner, min.name, min.desc) + '\n';
  159. throw new KilimException(msg);
  160. }
  161. if (classFlow.superName != null) {
  162. checkStatus(classFlow.superName, name, desc);
  163. }
  164. if (classFlow.interfaces != null) {
  165. for (Object ifc: classFlow.interfaces) {
  166. checkStatus((String) ifc, name, desc);
  167. }
  168. }
  169. }
  170. private void checkStatus(String superClassName, String methodName, String desc) throws KilimException {
  171. int status = detector.getPausableStatus(superClassName, methodName, desc);
  172. if ((status == Detector.PAUSABLE_METHOD_FOUND && !hasPausableAnnotation)) {
  173. throw new KilimException("Base class method is pausable, derived class is not: " +
  174. "\nBase class = " + superClassName +
  175. "\nDerived class = " + this.classFlow.name +
  176. "\nMethod = " + methodName + desc);
  177. }
  178. if (status == Detector.METHOD_NOT_PAUSABLE && hasPausableAnnotation) {
  179. throw new KilimException("Base class method is not pausable, but derived class is: " +
  180. "\nBase class = " + superClassName +
  181. "\nDerived class = " + this.classFlow.name +
  182. "\nMethod = " + methodName + desc);
  183. }
  184. }
  185. private String toString(String className, String methName, String desc) {
  186. return className.replace('/', '.') + '.' + methName + desc;
  187. }
  188. @Override
  189. public void visitMethodInsn(int opcode, String owner, String name, String desc,boolean itf) {
  190. super.visitMethodInsn(opcode, owner, name, desc, itf);
  191. // The only reason for adding to pausableMethods is to create a BB for pausable
  192. // method call sites. If the class is already woven, we don't need this
  193. // functionality.
  194. if (!classFlow.isWoven) {
  195. int methodStatus = detector.getPausableStatus(owner, name, desc);
  196. if (methodStatus == Detector.PAUSABLE_METHOD_FOUND) {
  197. MethodInsnNode min = (MethodInsnNode)instructions.get(instructions.size()-1);
  198. pausableMethods.add(min);
  199. }
  200. }
  201. }
  202. @Override
  203. public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
  204. if (!classFlow.isWoven) {
  205. if (bsm.getOwner().equals("java/lang/invoke/LambdaMetafactory")) {
  206. Handle lambdaBody = (Handle)bsmArgs[1];
  207. String lambdaDesc = lambdaBody.getDesc();
  208. if (detector.isPausable(lambdaBody.getOwner(), lambdaBody.getName(), lambdaDesc)) {
  209. hasPausableInvokeDynamic = true;
  210. }
  211. }
  212. }
  213. super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
  214. }
  215. @Override
  216. public void visitLabel(Label label) {
  217. setLabel(instructions.size(), super.getLabelNode(label));
  218. }
  219. @Override
  220. public void visitLineNumber(int line, Label start) {
  221. LabelNode ln = getLabelNode(start);
  222. lineNumberNodes.put(instructions.size(), new LineNumberNode(line, ln));
  223. }
  224. void visitLineNumbers(MethodVisitor mv) {
  225. for (LineNumberNode node : lineNumberNodes.values()) {
  226. mv.visitLineNumber(node.line, node.start.getLabel());
  227. }
  228. }
  229. @Override
  230. public void visitFrame(int type, int nLocal, Object[] local, int nStack,
  231. Object[] stack) {
  232. frameNodes.put(instructions.size(), new FrameNode(type, nLocal, local, nStack, stack));
  233. }
  234. private void inlineSubroutines() throws KilimException {
  235. markPausableJSRs();
  236. while (true) {
  237. ArrayList<BasicBlock> newBBs = null;
  238. for (BasicBlock bb: basicBlocks) {
  239. if (bb.hasFlag(INLINE_CHECKED)) continue;
  240. bb.setFlag(INLINE_CHECKED);
  241. if (bb.lastInstruction() == JSR) {
  242. newBBs = bb.inline();
  243. if (newBBs != null) {
  244. break;
  245. }
  246. }
  247. }
  248. if (newBBs == null) {
  249. break;
  250. }
  251. int id = basicBlocks.size();
  252. for (BasicBlock bb: newBBs) {
  253. bb.setId(id++);
  254. basicBlocks.add(bb);
  255. }
  256. }
  257. // If there are any pausable subroutines, modify the JSRs/RETs to
  258. // GOTOs
  259. for (BasicBlock bb: basicBlocks) {
  260. bb.changeJSR_RET_toGOTOs();
  261. }
  262. }
  263. private void markPausableJSRs() throws KilimException {
  264. for (BasicBlock bb: basicBlocks) {
  265. bb.checkPausableJSR();
  266. }
  267. }
  268. boolean isPausableMethodInsn(MethodInsnNode min) {
  269. return pausableMethods.contains(min);
  270. }
  271. @Override
  272. public String toString() {
  273. ArrayList<BasicBlock> ret = getBasicBlocks();
  274. Collections.sort(ret);
  275. return ret.toString();
  276. }
  277. public BBList getBasicBlocks() {
  278. return basicBlocks;
  279. }
  280. private void preAssignCatchHandlers() {
  281. @SuppressWarnings("unchecked")
  282. ArrayList<TryCatchBlockNode> tcbs = (ArrayList<TryCatchBlockNode>) tryCatchBlocks;
  283. /// aargh. I'd love to create an array of Handler objects, but generics
  284. // doesn't care for it.
  285. if (tcbs.size() == 0) return;
  286. ArrayList<Handler> handlers= new ArrayList<Handler>(tcbs.size());
  287. for (int i = 0; i < tcbs.size(); i++) {
  288. TryCatchBlockNode tcb = tcbs.get(i);
  289. handlers.add(new Handler(
  290. getLabelPosition(tcb.start),
  291. getLabelPosition(tcb.end) - 1, // end is inclusive
  292. tcb.type,
  293. getOrCreateBasicBlock(tcb.handler),
  294. i));
  295. }
  296. origHandlers = new ArrayList(handlers);
  297. Collections.sort(handlers, Handler.startComparator());
  298. buildHandlerMap(handlers);
  299. }
  300. private void assignCatchHandlers() {
  301. if (origHandlers==null) return;
  302. for (BasicBlock bb : basicBlocks) {
  303. bb.chooseCatchHandlers(origHandlers);
  304. }
  305. origHandlers = null;
  306. handlerMap = null;
  307. }
  308. private int [] handlerMap;
  309. private void buildHandlerMap(ArrayList<Handler> sorted) {
  310. handlerMap = new int[instructions.size()];
  311. for (int ki=0; ki < handlerMap.length; ki++) handlerMap[ki] = -1;
  312. int ki = 0;
  313. for (int kh=0; kh < sorted.size(); kh++) {
  314. Handler ho = sorted.get(kh);
  315. for (; ki <= ho.from; ki++)
  316. handlerMap[ki] = ho.from;
  317. }
  318. }
  319. /** return the next handler.from >= start, else -1 - valid only until catch handlers are assigned */
  320. int mapHandler(int start) {
  321. if (handlerMap==null || start >= handlerMap.length) return -1;
  322. int map = handlerMap[start];
  323. return map;
  324. }
  325. void buildBasicBlocks() {
  326. // preparatory phase
  327. int numInstructions = instructions.size();
  328. basicBlocks = new BBList();
  329. // Note: i modified within the loop
  330. for (int i = 0; i < numInstructions; i++) {
  331. LabelNode l = getOrCreateLabelAtPos(i);
  332. BasicBlock bb = getOrCreateBasicBlock(l);
  333. i = bb.initialize(i); // i now points to the last instruction in bb.
  334. basicBlocks.add(bb);
  335. }
  336. }
  337. private boolean calcBornOnce() {
  338. BBList bbs = getBasicBlocks();
  339. LinkedList<BasicBlock> pending = new LinkedList();
  340. boolean changed = false;
  341. pending.add(bbs.get(0));
  342. while (! pending.isEmpty()) {
  343. BasicBlock bb = pending.pop();
  344. if (bb.visited) continue;
  345. bb.visited = true;
  346. for (Handler ho : bb.handlers) {
  347. changed |= ho.catchBB.usage.evalBornIn(bb.usage,null);
  348. pending.addFirst(ho.catchBB);
  349. }
  350. BitSet combo = bb.usage.getCombo();
  351. for (BasicBlock so : bb.successors) {
  352. changed |= so.usage.evalBornIn(null,combo);
  353. pending.addFirst(so);
  354. }
  355. }
  356. return changed;
  357. }
  358. private void calcBornUsage() {
  359. // defined vars don't mix into handlers, so do 2 passes
  360. // propogate predecessor to successor (def,born) and handler (born)
  361. // mix def into born
  362. while (true)
  363. if (! calcBornOnce()) break;
  364. for (BasicBlock bb : getBasicBlocks())
  365. bb.usage.mergeBorn();
  366. }
  367. /** print the bit by bit liveness data after calculation */
  368. public static boolean debugPrintLiveness = false;
  369. /**
  370. * In live var analysis a BB asks its successor (in essence) about which
  371. * vars are live, mixes it with its own uses and defs and passes on a
  372. * new list of live vars to its predecessors. Since the information
  373. * bubbles up the chain, we iterate the list in reverse order, for
  374. * efficiency. We could order the list topologically or do a depth-first
  375. * spanning tree, but it seems like overkill for most bytecode
  376. * procedures. The order of computation doesn't affect the correctness;
  377. * it merely changes the number of iterations to reach a fixpoint.
  378. *
  379. * the algorithm has been updated to track vars that been born, ie def'd by a parameter
  380. */
  381. private void doLiveVarAnalysis() {
  382. ArrayList<BasicBlock> bbs = getBasicBlocks();
  383. Collections.sort(bbs); // sorts in increasing startPos order
  384. setArgsBorn(bbs.get(0));
  385. boolean changed;
  386. calcBornUsage();
  387. do {
  388. changed = false;
  389. for (int i = bbs.size() - 1; i >= 0; i--) {
  390. changed = bbs.get(i).flowVarUsage() || changed;
  391. }
  392. } while (changed);
  393. if (debugPrintLiveness) printUsage(bbs);
  394. }
  395. private void setArgsBorn(BasicBlock bb) {
  396. BitSet born = new BitSet(bb.flow.maxLocals);
  397. int offset = 0;
  398. if (!isStatic())
  399. born.set(offset++);
  400. for (String arg : TypeDesc.getArgumentTypes(desc)) {
  401. born.set(offset);
  402. offset += TypeDesc.isDoubleWord(arg) ? 2 : 1;
  403. }
  404. bb.usage.initBorn(born);
  405. }
  406. void printUsage(ArrayList<BasicBlock> bbs) {
  407. System.out.println(name);
  408. if (bbs==null) bbs = getBasicBlocks();
  409. for (BasicBlock bb : bbs) {
  410. Usage uu = bb.usage;
  411. String range = String.format("%4d %4d: ",bb.startPos,bb.endPos);
  412. System.out.print(range + uu.toStringBits(" "));
  413. System.out.println(" -- " + bb.printGeniology());
  414. }
  415. System.out.format("\n\n");
  416. }
  417. /**
  418. * In the first pass (buildBasicBlocks()), we create BBs whenever we
  419. * encounter a label. We don't really know until we are done with that
  420. * pass whether a label is the target of a branch instruction or it is
  421. * there because of an exception handler. See coalesceWithFollowingBlock()
  422. * for more detail.
  423. */
  424. private void consolidateBasicBlocks() {
  425. BBList newBBs = new BBList(basicBlocks.size());
  426. int pos = 0;
  427. for (BasicBlock bb: basicBlocks) {
  428. if (!bb.hasFlag(COALESCED)) {
  429. bb.coalesceTrivialFollowers();
  430. // The original bb's followers should have been marked as processed.
  431. bb.setId(pos++);
  432. newBBs.add(bb);
  433. }
  434. }
  435. basicBlocks = newBBs;
  436. assert checkNoBasicBlockLeftBehind();
  437. }
  438. private boolean checkNoBasicBlockLeftBehind() { // like "no child left behind"
  439. ArrayList<BasicBlock> bbs = basicBlocks;
  440. HashSet<BasicBlock> hs = new HashSet<BasicBlock>(bbs.size() * 2);
  441. hs.addAll(bbs);
  442. int prevBBend = -1;
  443. for (BasicBlock bb: bbs) {
  444. assert bb.isInitialized() : "BB not inited: " + bb;
  445. assert bb.startPos == prevBBend + 1;
  446. for (BasicBlock succ: bb.successors) {
  447. assert succ.isInitialized() : "Basic block not inited: " + succ +"\nSuccessor of " + bb;
  448. assert hs.contains(succ) :
  449. "BB not found:\n" + succ;
  450. }
  451. prevBBend = bb.endPos;
  452. }
  453. assert bbs.get(bbs.size()-1).endPos == instructions.size()-1;
  454. return true;
  455. }
  456. private void dataFlow() {
  457. workset = new PriorityQueue<BasicBlock>(instructions.size(), new BBComparator());
  458. //System.out.println("Method: " + this.name);
  459. BasicBlock startBB = getBasicBlocks().get(0);
  460. assert startBB != null : "Null starting block in flowTypes()";
  461. startBB.startFrame = new Frame(classFlow.getClassDescriptor(), this);
  462. enqueue(startBB);
  463. while (!workset.isEmpty()) {
  464. BasicBlock bb = dequeue();
  465. bb.interpret();
  466. }
  467. }
  468. void setLabel(int pos, LabelNode l) {
  469. for (int i = pos - posToLabelMap.size() + 1; i >= 0; i--) {
  470. // pad with nulls ala perl
  471. posToLabelMap.add(null);
  472. }
  473. posToLabelMap.set(pos, l);
  474. labelToPosMap.put(l, pos);
  475. }
  476. LabelNode getOrCreateLabelAtPos(int pos) {
  477. LabelNode ret = null;
  478. if (pos < posToLabelMap.size()) {
  479. ret = posToLabelMap.get(pos);
  480. }
  481. if (ret == null) {
  482. ret = new LabelNode();
  483. setLabel(pos, ret);
  484. }
  485. return ret;
  486. }
  487. int getLabelPosition(LabelNode l) {
  488. return labelToPosMap.get(l);
  489. }
  490. BasicBlock getOrCreateBasicBlock(LabelNode l) {
  491. BasicBlock ret = labelToBBMap.get(l);
  492. if (ret == null) {
  493. ret = new BasicBlock(this, l);
  494. Object oldVal = labelToBBMap.put(l, ret);
  495. assert oldVal == null : "Duplicate BB created at label";
  496. }
  497. return ret;
  498. }
  499. BasicBlock getBasicBlock(LabelNode l) {
  500. return labelToBBMap.get(l);
  501. }
  502. private BasicBlock dequeue() {
  503. BasicBlock bb = workset.poll();
  504. bb.unsetFlag(ENQUEUED);
  505. return bb;
  506. }
  507. void enqueue(BasicBlock bb) {
  508. assert bb.startFrame != null : "Enqueued null start frame";
  509. if (!bb.hasFlag(ENQUEUED)) {
  510. workset.add(bb);
  511. bb.setFlag(ENQUEUED);
  512. }
  513. }
  514. public LabelNode getLabelAt(int pos) {
  515. return (pos < posToLabelMap.size()) ? posToLabelMap.get(pos) : null;
  516. }
  517. void addInlinedBlock(BasicBlock bb) {
  518. bb.setId(basicBlocks.size());
  519. basicBlocks.add(bb);
  520. }
  521. public int getNumArgs() {
  522. int ret = TypeDesc.getNumArgumentTypes(desc);
  523. if (!isStatic()) ret++;
  524. return ret;
  525. }
  526. public boolean isPausable() {
  527. return hasPausableAnnotation;
  528. }
  529. public void setPausable(boolean isPausable) {
  530. hasPausableAnnotation = isPausable;
  531. }
  532. public static void acceptAnnotation(final AnnotationVisitor av, final String name,
  533. final Object value) {
  534. if (value instanceof String[]) {
  535. String[] typeconst = (String[]) value;
  536. av.visitEnum(name, typeconst[0], typeconst[1]);
  537. } else if (value instanceof AnnotationNode) {
  538. AnnotationNode an = (AnnotationNode) value;
  539. an.accept(av.visitAnnotation(name, an.desc));
  540. } else if (value instanceof List<?>) {
  541. AnnotationVisitor v = av.visitArray(name);
  542. List<?> array = (List<?>) value;
  543. for (int j = 0; j < array.size(); ++j) {
  544. acceptAnnotation(v, null, array.get(j));
  545. }
  546. v.visitEnd();
  547. } else {
  548. av.visit(name, value);
  549. }
  550. }
  551. public boolean isAbstract() {
  552. return ((this.access & Opcodes.ACC_ABSTRACT) != 0);
  553. }
  554. public boolean isStatic() {
  555. return ((this.access & ACC_STATIC) != 0);
  556. }
  557. public boolean isBridge() {
  558. return ((this.access & ACC_VOLATILE) != 0);
  559. }
  560. public void resetLabels() {
  561. for (int i = 0; i < posToLabelMap.size(); i++) {
  562. LabelNode ln = posToLabelMap.get(i);
  563. if (ln != null) {
  564. ln.resetLabel();
  565. }
  566. }
  567. }
  568. boolean needsWeaving() {
  569. return isPausable() || hasPausableInvokeDynamic;
  570. }
  571. }