PageRenderTime 38ms CodeModel.GetById 2ms app.highlight 30ms RepoModel.GetById 1ms app.codeStats 0ms

/src/kilim/analysis/MethodWeaver.java

http://github.com/kilim/kilim
Java | 659 lines | 470 code | 67 blank | 122 comment | 110 complexity | 3cabb109e4b4290962edbd444d33468c 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.D_FIBER_LAST_ARG;
  9import static kilim.Constants.D_INT;
 10import static kilim.Constants.D_VOID;
 11import static kilim.Constants.FIBER_CLASS;
 12import static kilim.Constants.TASK_CLASS;
 13import static kilim.analysis.VMType.TOBJECT;
 14import static kilim.analysis.VMType.loadVar;
 15import static org.objectweb.asm.Opcodes.ALOAD;
 16import static org.objectweb.asm.Opcodes.ASTORE;
 17import static org.objectweb.asm.Opcodes.DUP;
 18import static org.objectweb.asm.Opcodes.GETFIELD;
 19import static org.objectweb.asm.Opcodes.GOTO;
 20import static org.objectweb.asm.Opcodes.INVOKESTATIC;
 21import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
 22import static org.objectweb.asm.Opcodes.RETURN;
 23
 24import java.util.ArrayList;
 25import java.util.List;
 26import java.util.ListIterator;
 27
 28import kilim.Constants;
 29import kilim.mirrors.Detector;
 30
 31import org.objectweb.asm.AnnotationVisitor;
 32import org.objectweb.asm.Attribute;
 33import org.objectweb.asm.ClassVisitor;
 34import org.objectweb.asm.Handle;
 35import org.objectweb.asm.MethodVisitor;
 36import org.objectweb.asm.Opcodes;
 37import org.objectweb.asm.Type;
 38import org.objectweb.asm.tree.AbstractInsnNode;
 39import org.objectweb.asm.tree.AnnotationNode;
 40import org.objectweb.asm.tree.InvokeDynamicInsnNode;
 41import org.objectweb.asm.tree.LabelNode;
 42import org.objectweb.asm.tree.LdcInsnNode;
 43import org.objectweb.asm.tree.LocalVariableNode;
 44import org.objectweb.asm.tree.LookupSwitchInsnNode;
 45import org.objectweb.asm.tree.TableSwitchInsnNode;
 46import org.objectweb.asm.tree.TryCatchBlockNode;
 47
 48/**
 49 * This class takes the basic blocks from a MethodFlow and generates 
 50 * all the extra code to support continuations.  
 51 */
 52
 53public class MethodWeaver {
 54
 55    private ClassWeaver           classWeaver;
 56
 57    private MethodFlow            methodFlow;
 58
 59    private boolean               isPausable;
 60
 61    private int                   maxVars;
 62
 63    private int                   maxStack;
 64    
 65    private boolean               isSAM;
 66
 67    /**
 68     * The last parameter to a pausable method is a Fiber ref. The rest of the
 69     * code doesn't know this because we do local surgery, and so is likely to
 70     * stomp on the corresponding local var. We need to save this in a slot
 71     * beyond (the original) maxLocals that is a safe haven for keeping the
 72     * fiberVar.
 73     */
 74    private int                   fiberVar;
 75    private int                   numWordsInSig;
 76    private ArrayList<CallWeaver> callWeavers = new ArrayList<CallWeaver>(5);
 77
 78    private Detector detector;
 79
 80    MethodWeaver(ClassWeaver cw, Detector detector, MethodFlow mf, boolean isSAM) {
 81        this.detector = detector;
 82        this.classWeaver = cw;
 83        this.methodFlow = mf;
 84        isPausable = mf.isPausable();
 85        fiberVar =  methodFlow.maxLocals;
 86        maxVars = fiberVar + 1;
 87        maxStack = methodFlow.maxStack + 1; // plus Fiber
 88        this.isSAM = isSAM;
 89        if (!mf.isAbstract()) {
 90            createCallWeavers();
 91        }
 92    }
 93
 94    
 95    public void accept(ClassVisitor cv) {
 96        MethodFlow mf = methodFlow;
 97        String[] exceptions = ClassWeaver.toStringArray(mf.exceptions);
 98        String desc = mf.desc;
 99        String sig = mf.signature;
100        int access = mf.access;
101        if (mf.isPausable()) {
102            access &= ~Opcodes.ACC_VARARGS;
103            if (!isSAM) {
104                desc = desc.replace(")", D_FIBER_LAST_ARG);
105                if (sig != null)
106                    sig = sig.replace(")", D_FIBER_LAST_ARG);
107            }
108        }
109        MethodVisitor mv = cv.visitMethod(access, mf.name, desc, sig, exceptions);
110
111        if (!mf.isAbstract()) {
112            if (mf.needsWeaving()) {
113                accept(mv);
114            } else {
115                mf.accept(mv);
116            }
117        } else {
118        	mf.accept(mv);
119        }
120    }
121    
122    void accept(MethodVisitor mv) {
123        visitAttrs(mv);
124        visitCode(mv);
125        mv.visitEnd();
126    }
127
128    private void visitAttrs(MethodVisitor mv) {
129        MethodFlow mf = methodFlow;
130        // visits the method attributes
131        int i, j, n;
132        if (mf.annotationDefault != null) {
133            AnnotationVisitor av = mv.visitAnnotationDefault();
134            MethodFlow.acceptAnnotation(av, null, mf.annotationDefault);
135            av.visitEnd();
136        }
137        n = mf.visibleAnnotations == null ? 0 : mf.visibleAnnotations.size();
138        for (i = 0; i < n; ++i) {
139            AnnotationNode an = (AnnotationNode) mf.visibleAnnotations.get(i);
140            an.accept(mv.visitAnnotation(an.desc, true));
141        }
142        n = mf.invisibleAnnotations == null ? 0
143                : mf.invisibleAnnotations.size();
144        for (i = 0; i < n; ++i) {
145            AnnotationNode an = (AnnotationNode) mf.invisibleAnnotations.get(i);
146            an.accept(mv.visitAnnotation(an.desc, false));
147        }
148        n = mf.visibleParameterAnnotations == null ? 0
149                : mf.visibleParameterAnnotations.length;
150        for (i = 0; i < n; ++i) {
151            List<?> l = mf.visibleParameterAnnotations[i];
152            if (l == null) {
153                continue;
154            }
155            for (j = 0; j < l.size(); ++j) {
156                AnnotationNode an = (AnnotationNode) l.get(j);
157                an.accept(mv.visitParameterAnnotation(i, an.desc, true));
158            }
159        }
160        n = mf.invisibleParameterAnnotations == null ? 0
161                : mf.invisibleParameterAnnotations.length;
162        for (i = 0; i < n; ++i) {
163            List<?> l = mf.invisibleParameterAnnotations[i];
164            if (l == null) {
165                continue;
166            }
167            for (j = 0; j < l.size(); ++j) {
168                AnnotationNode an = (AnnotationNode) l.get(j);
169                an.accept(mv.visitParameterAnnotation(i, an.desc, false));
170            }
171        }
172        n = mf.attrs == null ? 0 : mf.attrs.size();
173        for (i = 0; i < n; ++i) {
174            mv.visitAttribute((Attribute) mf.attrs.get(i));
175        }
176    }
177    
178    private void visitCode(MethodVisitor mv) {
179        mv.visitCode();
180        methodFlow.resetLabels();
181        visitTryCatchBlocks(mv);
182        visitInstructions(mv);
183        visitLocals(mv);
184        visitLineNumbers(mv);
185        mv.visitMaxs(maxStack, maxVars);
186    }
187
188    private void visitLineNumbers(MethodVisitor mv) {
189        methodFlow.visitLineNumbers(mv);
190    }
191  
192    private void visitLocals(MethodVisitor mv) {
193        for (Object l: methodFlow.localVariables) {
194            ((LocalVariableNode)l).accept(mv);
195        }
196    }
197
198    private void visitInstructions(MethodVisitor mv) {
199        MethodFlow mf = methodFlow;
200        genPrelude(mv);
201        BasicBlock lastBB = null;
202        boolean dsl = dslName.equals(mf.name) & dslDesc.equals(mf.desc);
203        if (dsl)
204            preweaveDeserializeLambda();
205        for (BasicBlock bb : mf.getBasicBlocks()) {
206            int from = bb.startPos;
207            
208            if (bb.isPausable() && bb.startFrame != null) {
209                genPausableMethod(mv, bb);
210                from = bb.startPos + 1; // first instruction is consumed
211            } else if (bb.isCatchHandler()) {
212                List<CallWeaver> cwList = getCallsUnderCatchBlock(bb);
213                if (cwList != null) {
214                    genException(mv, bb, cwList);
215                    from = bb.startPos + 1; // first instruction is consumed
216                } // else no different from any other block
217            }
218            int to = bb.endPos;
219            for (int i = from; i <= to; i++) {
220                LabelNode l = mf.getLabelAt(i);
221                if (l != null) {
222                    l.accept(mv);
223                }
224                AbstractInsnNode ain = bb.getInstruction(i);
225                if (ain.getOpcode() == Constants.INVOKEDYNAMIC) {
226                    transformIndyBootstrap(mv, ain);
227                } else {
228                    ain.accept(mv);
229                }
230            }
231            lastBB = bb;
232        }
233        if (lastBB != null) {
234            LabelNode l = methodFlow.getLabelAt(lastBB.endPos+1);
235            if (l != null) {
236                l.accept(mv);
237            }
238        }
239    }
240    static String dslName = "$deserializeLambda$";
241    static String dslDesc = "(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;";
242    private static String addFiber(String type) {
243        return type.replace(")", Constants.D_FIBER_LAST_ARG);
244    }
245
246    /** 
247     * at least with openjdk 8-11, deserializing lambdas checks the signature or the target.
248     * FIXME: this method is implementation dependent (probably unfixable, but want this line to show in a grep)
249     */
250    public void preweaveDeserializeLambda() {
251        int maxState = 22;
252        int state = 0;
253        String cname = null;
254        String mname = null;
255
256        int num = methodFlow.instructions.size(); 
257        for (int ii=0; ii < num; ii++) {
258            AbstractInsnNode node = methodFlow.instructions.get(ii);
259
260            int opcode = node.getOpcode();
261            if (opcode != Opcodes.LDC) {
262                if (opcode==Opcodes.INVOKEDYNAMIC || opcode==Opcodes.LOOKUPSWITCH)
263                    state = 0;
264                continue;
265            }
266            LdcInsnNode ldc = (LdcInsnNode) node;
267
268
269            switch (state) {
270                case 0: cname = (String) ldc.cst; break;
271                case 1: mname = (String) ldc.cst; break;
272                case 2:
273                    String mdesc = (String) ldc.cst;
274                    if (detector.getPausableStatus(cname,mname,mdesc)==Detector.PAUSABLE_METHOD_FOUND)
275                        ldc.cst = addFiber(mdesc);
276                    else
277                        state = maxState;
278                    break;
279
280
281                case 4: ldc.cst = addFiber((String) ldc.cst); break;
282            }
283            state++;
284        }
285    }
286
287    private void transformIndyBootstrap(MethodVisitor mv, AbstractInsnNode ain) {
288        InvokeDynamicInsnNode indy = (InvokeDynamicInsnNode)ain;
289        Object[]bsmArgs = indy.bsmArgs;
290        // Is it a lambda conversion
291        if (indy.bsm.getOwner().equals("java/lang/invoke/LambdaMetafactory")) {
292            Handle lambdaBody = (Handle)bsmArgs[1];
293            String desc = lambdaBody.getDesc();
294            if (detector.isPausable(lambdaBody.getOwner(), lambdaBody.getName(), desc)) {
295                bsmArgs[0] = addFiberType((Type)bsmArgs[0]);
296                bsmArgs[1] = new Handle(lambdaBody.getTag(), 
297                                        lambdaBody.getOwner(), 
298                                        lambdaBody.getName(), 
299                                        desc.replace(")", D_FIBER_LAST_ARG),
300                                        lambdaBody.isInterface());
301                bsmArgs[2] = addFiberType((Type)bsmArgs[2]);
302            }
303        }
304        ain.accept(mv);
305    }
306
307    private static Type addFiberType(Type type) {
308        String typeDesc = type.toString().replace(")", D_FIBER_LAST_ARG);
309        return Type.getType(typeDesc);
310    }
311
312
313
314    private List<CallWeaver> getCallsUnderCatchBlock(BasicBlock catchBB) {
315        List<CallWeaver> cwList = null; // create it lazily
316        for (CallWeaver cw: callWeavers) {
317            for (Handler h: cw.bb.handlers) {
318                if (h.catchBB == catchBB) {
319                    if (cwList == null) {
320                        cwList = new ArrayList<CallWeaver>(callWeavers.size()); 
321                    }
322                    if (!cwList.contains(cw)) {
323                    cwList.add(cw);
324                }
325            }
326        }
327        }
328        return cwList;
329    }
330
331    /**
332     * For a method invocation f(...), this method assumes that the arguments to
333     * the call have already been pushed in. We need to push in the Fiber as the
334     * final argument, make the call, then add the code for post-calls, then
335     * leave it to visitInstructions() to resume visiting the remaining
336     * instructions in the block
337     * 
338     * <pre>
339     *  F_CALL:
340     *    aload &lt;fiberVar&gt;
341     *    invokevirtual fiber.down() ;; returns Fiber
342     *    ... invoke ....
343     *    aload &lt;fiberVar&gt;
344     *    ... post call code
345     *  F_RESUME: 
346     * </pre>
347     * 
348     * @param bb
349     * The BasicBlock that contains the pausable method invocation as the first
350     * instruction
351     * @param mv
352     */
353    private void genPausableMethod(MethodVisitor mv, BasicBlock bb) {
354        CallWeaver caw = null;
355        if (bb.isGetCurrentTask()) {
356            genGetCurrentTask(mv, bb);
357            return;
358        }
359        for (CallWeaver cw : callWeavers) {
360            if (cw.getBasicBlock() == bb) {
361                caw = cw;
362                break;
363            }
364        }
365        caw.genCall(mv);
366        caw.genPostCall(mv);
367    }
368    
369    /*
370     * The Task.getCurrentTask() method is marked pausable to force
371     * the caller to be pausable too. But the method doesn't really
372     * pause; it merely looks up the task from the fiber. This is a
373     * special case where the call to getCurrentTask is replaced by
374     * <pre>
375     *   load fiberVar
376     *   getfield task
377     * @param mv
378     */
379    void genGetCurrentTask(MethodVisitor mv, BasicBlock bb) {
380        bb.startLabel.accept(mv);
381        loadVar(mv, TOBJECT, getFiberVar());
382        mv.visitFieldInsn(GETFIELD, FIBER_CLASS, "task", Constants.D_TASK);
383    }
384
385    private boolean hasGetCurrentTask() {
386        MethodFlow mf = methodFlow;
387        for (BasicBlock bb : mf.getBasicBlocks()) {
388            if (!bb.isPausable() || bb.startFrame==null) continue;
389            if (bb.isGetCurrentTask()) return true;
390        }
391        return false;
392    }
393    private void createCallWeavers() {
394        MethodFlow mf = methodFlow;
395        for (BasicBlock bb : mf.getBasicBlocks()) {
396            if (!bb.isPausable() || bb.startFrame==null) continue;
397            // No prelude needed for Task.getCurrentTask(). 
398            if (bb.isGetCurrentTask()) continue; 
399            CallWeaver cw = new CallWeaver(this, detector, bb);
400            callWeavers.add(cw);
401        }
402    }
403
404    /**
405     * 
406     * Say there are two invocations to two pausable methods obj.f(int)
407     * (virtual) and fs(double) (a static call) ; load fiber from last arg, and
408     * save it in a fresh register ; lest it gets stomped on. This is because we
409     * only patch locally, and don't change the other instructions.
410     * 
411     * <pre>
412     *     aload lastVar
413     *     dup
414     *     astore fiberVar 
415     *     switch (fiber.pc) { 
416     *       default: 0: START 
417     *       1: F_PASS_DOWN 
418     *       2: FS_PASS_DOWN 
419     *     }
420     * </pre>
421     */
422    private void genPrelude(MethodVisitor mv) {
423        if (!methodFlow.isPausable()) return;
424        if (callWeavers.size() == 0 && (!hasGetCurrentTask())) {
425            // Method has been marked pausable, but does not call any pausable methods, nor Task.getCurrentTask.  
426            // Prelude is not needed at all.
427            return; 
428        }
429        
430        MethodFlow mf = methodFlow;
431        // load fiber from last var
432        int lastVar = getFiberArgVar();
433       
434        mv.visitVarInsn(ALOAD, lastVar);
435        if (lastVar < fiberVar) {
436            if (callWeavers.size() > 0) {
437                mv.visitInsn(DUP); // for storing into fiberVar
438            }
439            mv.visitVarInsn(ASTORE, getFiberVar());
440        }
441        
442        if (callWeavers.size() == 0) {
443          // No pausable method calls, but Task.getCurrentTask() is present. 
444          // We don't need the rest of the prelude.
445           return; 
446        }
447
448        mv.visitFieldInsn(GETFIELD, FIBER_CLASS, "pc", D_INT);
449        // The prelude doesn't need more than two words in the stack.
450        // The callweaver gen* methods may need more. 
451        ensureMaxStack(2);
452
453        // switch stmt
454        LabelNode startLabel = mf.getOrCreateLabelAtPos(0);
455        LabelNode errLabel = new LabelNode();
456        
457        LabelNode[] labels = new LabelNode[callWeavers.size() + 1];
458        labels[0] = startLabel;
459        for (int i = 0; i < callWeavers.size(); i++) {
460            labels[i + 1] = new LabelNode();
461        }
462        new TableSwitchInsnNode(0, callWeavers.size(), errLabel, labels).accept(mv);
463        
464        errLabel.accept(mv);
465        mv.visitVarInsn(ALOAD, getFiberVar());
466        mv.visitMethodInsn(INVOKEVIRTUAL, FIBER_CLASS, "wrongPC", "()V", false);
467        // Generate pass through down code, one for each pausable method
468        // invocation
469        int last = callWeavers.size() - 1;
470        for (int i = 0; i <= last; i++) {
471            CallWeaver cw = callWeavers.get(i);
472            labels[i+1].accept(mv);
473            cw.genRewind(mv);
474        }
475        startLabel.accept(mv);
476    }
477
478    boolean isStatic() {
479        return methodFlow.isStatic();
480    }
481
482    int getFiberArgVar() {
483        int lastVar = getNumWordsInSig();
484        if (!isStatic()) {
485            lastVar++;
486        }
487        return lastVar;
488    }
489
490    /*
491     * The number of words in the argument; doubles/longs occupy
492     * two local vars.
493     */
494    int getNumWordsInSig() {
495        if (numWordsInSig != -1) {
496            String[]args = TypeDesc.getArgumentTypes(methodFlow.desc);
497            int size = 0;
498            for (int i = 0; i < args.length; i++) {
499                size += TypeDesc.isDoubleWord(args[i]) ? 2 : 1;
500            }
501            numWordsInSig = size;
502        }
503        return numWordsInSig;
504    }
505
506    /**
507     * Generate code for only those catch blocks that are reachable
508     * from one or more pausable blocks. fiber.pc tells us which
509     * nested call possibly caused an exception, fiber.status tells us
510     * whether there is any state that needs to be restored, and
511     * fiber.curState gives us access to that state. 
512     * 
513     * ; Figure out which pausable method could have caused this.
514     * 
515     * switch (fiber.upEx()) {
516     *    0: goto NORMAL_EXCEPTION_HANDLING;
517     *    2: goto RESTORE_F
518     * }
519     * RESTORE_F:
520     *   if (fiber.curStatus == HAS_STATE) {
521     *      restore variables from the state. don't restore stack
522     *      goto NORMAL_EXCEPTION_HANDLING
523     *   }
524     * ... other RESTOREs
525     * 
526     * NORMAL_EXCEPTION_HANDLING:
527     */
528    private void genException(MethodVisitor mv, BasicBlock bb, List<CallWeaver> cwList) {
529        bb.startLabel.accept(mv);
530        LabelNode resumeLabel = new LabelNode();
531        VMType.loadVar(mv, VMType.TOBJECT, getFiberVar());
532        mv.visitMethodInsn(INVOKEVIRTUAL, FIBER_CLASS, "upEx", "()I", false);
533        // fiber.pc is on stack
534        LabelNode[] labels = new LabelNode[cwList.size()];
535        int[] keys = new int[cwList.size()];
536        for (int i = 0; i < cwList.size(); i++) {
537            labels[i] = new LabelNode();
538            keys[i] = callWeavers.indexOf(cwList.get(i)) + 1;
539        }
540        
541        new LookupSwitchInsnNode(resumeLabel, keys, labels).accept(mv);
542        int i = 0;
543        for (CallWeaver cw: cwList) {
544            if (i > 0) {
545                // This is the jump (to normal exception handling) for the previous
546                // switch case.
547                mv.visitJumpInsn(GOTO, resumeLabel.getLabel());
548            }
549            labels[i].accept(mv);
550            cw.genRestoreEx(mv, labels[i]);
551            i++;
552        }
553        
554        // Consume the first instruction because we have already consumed the
555        // corresponding label. (The standard visitInstructions code does a 
556        // visitLabel before visiting the instruction itself)
557        resumeLabel.accept(mv);
558        bb.getInstruction(bb.startPos).accept(mv);
559    }
560    
561    int getFiberVar() {
562        return fiberVar; // The first available slot
563    }
564
565    void visitTryCatchBlocks(MethodVisitor mv) {
566        MethodFlow mf = methodFlow;
567        ArrayList<BasicBlock> bbs = mf.getBasicBlocks();
568        ArrayList<Handler> allHandlers = new ArrayList<Handler>(bbs.size() * 2);
569        for (BasicBlock bb : bbs) {
570            allHandlers.addAll(bb.handlers);
571        }
572        allHandlers = Handler.consolidate(allHandlers);
573        for (Handler h : allHandlers) {
574            new TryCatchBlockNode(mf.getLabelAt(h.from), mf.getOrCreateLabelAtPos(h.to+1), h.catchBB.startLabel, h.type).accept(mv);
575        }
576    }
577
578    void ensureMaxVars(int numVars) {
579        if (numVars > maxVars) {
580            maxVars = numVars;
581        }
582    }
583
584    void ensureMaxStack(int numStack) {
585        if (numStack > maxStack) {
586            maxStack = numStack;
587        }
588    }
589
590    int getPC(CallWeaver weaver) {
591        for (int i = 0; i < callWeavers.size(); i++) {
592            if (callWeavers.get(i) == weaver)
593                return i + 1;
594        }
595        assert false : " No weaver found";
596        return 0;
597    }
598
599    public String createStateClass(ValInfoList valInfoList) {
600        return classWeaver.createStateClass(valInfoList);
601    }
602    
603    void makeNotWovenMethod(ClassVisitor cv, MethodFlow mf, boolean isSAM) {
604        if (classWeaver.classFlow.isJava7() && classWeaver.isInterface()) {
605            MethodVisitor mv = cv.visitMethod(mf.access, mf.name, mf.desc,
606                    mf.signature, ClassWeaver.toStringArray(mf.exceptions));
607            visitAttrs(mv);
608            mv.visitEnd();
609        } else {
610            // Turn of abstract modifier
611            int access = mf.access;
612            access &= ~Constants.ACC_ABSTRACT;
613            String desc = isSAM ? mf.desc.replace(")", Constants.D_FIBER_LAST_ARG) : mf.desc;
614            MethodVisitor mv = cv.visitMethod(access, mf.name, desc, 
615                    mf.signature, ClassWeaver.toStringArray(mf.exceptions));
616            mv.visitCode();
617            visitAttrs(mv);
618            boolean isInterface = classWeaver.isInterface() && !isSAM;
619            mv.visitMethodInsn(INVOKESTATIC, TASK_CLASS, "errNotWoven", "()V", isInterface);
620            
621            String rdesc = TypeDesc.getReturnTypeDesc(mf.desc);
622            // stack size depends on return type, because we want to load
623            // a constant of the appropriate size on the stack for 
624            // the corresponding xreturn instruction.
625            int stacksize = 0;
626            if (rdesc != D_VOID) {
627                // ICONST_0; IRETURN or ACONST_NULL; ARETURN etc.
628                stacksize = TypeDesc.isDoubleWord(rdesc) ? 2 : 1;
629                int vmt = VMType.toVmType(rdesc);
630                mv.visitInsn(VMType.constInsn[vmt]);
631                mv.visitInsn(VMType.retInsn[vmt]);
632            } else {
633                mv.visitInsn(RETURN);
634            }
635            
636            int numlocals;
637            if ((mf.access & Constants.ACC_ABSTRACT) != 0) {
638                // The abstract method doesn't contain the number of locals required to hold the
639                // args, so we need to calculate it.
640                numlocals = getNumWordsInSig() + 1 /* fiber */;
641                if (!mf.isStatic()) numlocals++;
642            } else {
643                numlocals = mf.maxLocals + 1;
644            }
645            mv.visitMaxs(stacksize, numlocals);
646            mv.visitEnd();
647        }
648
649    }
650    ClassWeaver getClassWeaver() {
651        return this.classWeaver;
652    }
653    
654    MethodFlow getMethodFlow() {
655        return this.methodFlow;
656    }
657}
658
659