PageRenderTime 76ms CodeModel.GetById 12ms app.highlight 55ms RepoModel.GetById 1ms app.codeStats 1ms

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