net.sandius.rembulan.compiler.gen.asm.RunMethod.java Source code

Java tutorial

Introduction

Here is the source code for net.sandius.rembulan.compiler.gen.asm.RunMethod.java

Source

/*
 * Copyright 2016 Miroslav Jan?ek
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.sandius.rembulan.compiler.gen.asm;

import net.sandius.rembulan.compiler.gen.CodeSegmenter;
import net.sandius.rembulan.compiler.gen.SegmentedCode;
import net.sandius.rembulan.compiler.gen.asm.helpers.ASMUtils;
import net.sandius.rembulan.compiler.ir.BasicBlock;
import net.sandius.rembulan.compiler.ir.Label;
import net.sandius.rembulan.impl.DefaultSavedState;
import net.sandius.rembulan.runtime.ExecutionContext;
import net.sandius.rembulan.runtime.ResolvedControlThrowable;
import net.sandius.rembulan.runtime.Resumable;
import net.sandius.rembulan.runtime.UnresolvedControlThrowable;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import static org.objectweb.asm.Opcodes.*;

class RunMethod {

    public final int LV_CONTEXT = 1;
    public final int LV_RESUME = 2;
    public final int LV_VARARGS = 3; // index of the varargs argument, if present

    public static final int ST_SHIFT_SEGMENT = 24;
    public static final int ST_SHIFT_LABELIDX = 16;

    private final ASMBytecodeEmitter context;
    private final List<MethodNode> methodNodes;
    private final boolean resumable;

    private final List<ClosureFieldInstance> closureFields;
    private final List<ConstFieldInstance> constFields;

    interface LabelResolver {
        boolean isLocalLabel(Label l);

        int labelStateIndex(Label l);
    }

    static int labelStateIdx(SegmentedCode.LabelEntry le) {
        return (le.segmentIdx << ST_SHIFT_SEGMENT) | (le.idx << ST_SHIFT_LABELIDX);
    }

    public RunMethod(ASMBytecodeEmitter context) {
        this.context = Objects.requireNonNull(context);

        final SegmentedCode segmentedCode = CodeSegmenter.segment(context.fn.code(),
                context.compilerSettings.nodeSizeLimit());

        this.methodNodes = new ArrayList<>();

        this.closureFields = new ArrayList<>();
        this.constFields = new ArrayList<>();

        if (segmentedCode.isSingleton()) {
            // as before
            BytecodeEmitVisitor visitor = new BytecodeEmitVisitor(context, this, context.slots, context.types,
                    closureFields, constFields, -1, new LabelResolver() {
                        @Override
                        public boolean isLocalLabel(Label l) {
                            return true;
                        }

                        @Override
                        public int labelStateIndex(Label l) {
                            throw new IllegalStateException();
                        }
                    });

            this.methodNodes.add(emitSingletonRunMethod(visitor, segmentedCode.segments().get(0)));
            this.resumable = visitor.isResumable();
        } else {
            // split up into multiple segments

            boolean resumable = false;
            for (int i = 0; i < segmentedCode.segments().size(); i++) {

                final int thisSegmentIdx = i;

                BytecodeEmitVisitor visitor = new BytecodeEmitVisitor(context, this, context.slots, context.types,
                        closureFields, constFields, i, new LabelResolver() {
                            @Override
                            public boolean isLocalLabel(Label l) {
                                return segmentedCode.labelEntry(l).segmentIdx == thisSegmentIdx;
                            }

                            @Override
                            public int labelStateIndex(Label l) {
                                return labelStateIdx(segmentedCode.labelEntry(l));
                            }
                        });

                this.methodNodes.add(emitSegmentedSubRunMethod(i, visitor, segmentedCode.segments().get(i)));
                resumable |= visitor.isResumable();
            }

            this.resumable = resumable;

            this.methodNodes.add(emitSegmentedRunMethod(segmentedCode.segments().size()));

            //         throw new UnsupportedOperationException();  // TODO
        }
    }

    public int numOfRegisters() {
        return context.slots.numSlots();
    }

    public int slotOffset() {
        return context.isVararg() ? LV_VARARGS + 1 : LV_VARARGS;
    }

    public boolean isResumable() {
        return resumable;
    }

    public String[] throwsExceptions() {
        return new String[] { Type.getInternalName(ResolvedControlThrowable.class) };
    }

    public boolean usesSnapshotMethod() {
        return isResumable();
    }

    private String snapshotMethodName() {
        return "snapshot";
    }

    private Type snapshotMethodType() {
        ArrayList<Type> args = new ArrayList<>();

        args.add(Type.INT_TYPE);
        if (context.isVararg()) {
            args.add(ASMUtils.arrayTypeFor(Object.class));
        }
        for (int i = 0; i < numOfRegisters(); i++) {
            args.add(Type.getType(Object.class));
        }
        return Type.getMethodType(context.savedStateClassType(), args.toArray(new Type[0]));
    }

    public MethodInsnNode snapshotMethodInvokeInsn() {
        return new MethodInsnNode(INVOKESPECIAL, context.thisClassType().getInternalName(), snapshotMethodName(),
                snapshotMethodType().getDescriptor(), false);
    }

    public MethodNode snapshotMethodNode() {
        MethodNode node = new MethodNode(ACC_PRIVATE, snapshotMethodName(), snapshotMethodType().getDescriptor(),
                null, null);

        InsnList il = node.instructions;
        LabelNode begin = new LabelNode();
        LabelNode end = new LabelNode();

        il.add(begin);

        il.add(new TypeInsnNode(NEW, Type.getInternalName(DefaultSavedState.class)));
        il.add(new InsnNode(DUP));

        // resumption point
        il.add(new VarInsnNode(ILOAD, 1));

        // registers
        int numRegs = numOfRegisters() + (context.isVararg() ? 1 : 0);
        int regOffset = context.isVararg() ? 3 : 2;

        il.add(ASMUtils.loadInt(numRegs));
        il.add(new TypeInsnNode(ANEWARRAY, Type.getInternalName(Object.class)));
        {
            for (int i = 0; i < numRegs; i++) {
                il.add(new InsnNode(DUP));
                il.add(ASMUtils.loadInt(i));
                il.add(new VarInsnNode(ALOAD, 2 + i));
                il.add(new InsnNode(AASTORE));
            }
        }

        il.add(ASMUtils.ctor(Type.getType(DefaultSavedState.class), Type.INT_TYPE,
                ASMUtils.arrayTypeFor(Object.class)));

        il.add(new InsnNode(ARETURN));

        il.add(end);

        List<LocalVariableNode> locals = node.localVariables;

        locals.add(new LocalVariableNode("this", context.thisClassType().getDescriptor(), null, begin, end, 0));
        locals.add(new LocalVariableNode("rp", Type.INT_TYPE.getDescriptor(), null, begin, end, 1));
        if (context.isVararg()) {
            locals.add(new LocalVariableNode("varargs", ASMUtils.arrayTypeFor(Object.class).getDescriptor(), null,
                    begin, end, 2));
        }
        for (int i = 0; i < numOfRegisters(); i++) {
            locals.add(new LocalVariableNode("r_" + i, Type.getDescriptor(Object.class), null, begin, end,
                    regOffset + i));
        }

        node.maxLocals = 2 + numOfRegisters();
        node.maxStack = 4 + 3; // 4 to get register array at top, +3 to add element to it

        return node;
    }

    public String methodName() {
        return "run";
    }

    private Type methodType(Type returnType) {
        ArrayList<Type> args = new ArrayList<>();

        args.add(Type.getType(ExecutionContext.class));
        args.add(Type.INT_TYPE);
        if (context.isVararg()) {
            args.add(ASMUtils.arrayTypeFor(Object.class));
        }
        for (int i = 0; i < numOfRegisters(); i++) {
            args.add(Type.getType(Object.class));
        }
        return Type.getMethodType(returnType, args.toArray(new Type[0]));
    }

    public Type methodType() {
        return methodType(Type.VOID_TYPE);
    }

    private Type subMethodType() {
        return methodType(context.savedStateClassType());
    }

    public AbstractInsnNode methodInvokeInsn() {
        return new MethodInsnNode(INVOKESPECIAL, context.thisClassType().getInternalName(), methodName(),
                methodType().getDescriptor(), false);
    }

    private InsnList errorState(LabelNode label) {
        InsnList il = new InsnList();
        il.add(label);
        il.add(ASMUtils.frameSame());
        il.add(new TypeInsnNode(NEW, Type.getInternalName(IllegalStateException.class)));
        il.add(new InsnNode(DUP));
        il.add(ASMUtils.ctor(IllegalStateException.class));
        il.add(new InsnNode(ATHROW));
        return il;
    }

    private InsnList dispatchTable(List<LabelNode> extLabels, List<LabelNode> resumptionLabels,
            LabelNode errorStateLabel) {
        InsnList il = new InsnList();

        assert (!extLabels.isEmpty());

        ArrayList<LabelNode> labels = new ArrayList<>();
        labels.addAll(extLabels);
        labels.addAll(resumptionLabels);
        LabelNode[] labelArray = labels.toArray(new LabelNode[labels.size()]);

        int min = 1 - extLabels.size();
        int max = resumptionLabels.size();

        il.add(new VarInsnNode(ILOAD, LV_RESUME));
        il.add(new TableSwitchInsnNode(min, max, errorStateLabel, labelArray));
        return il;
    }

    InsnList createSnapshot() {
        InsnList il = new InsnList();

        il.add(new VarInsnNode(ALOAD, 0)); // this
        il.add(new VarInsnNode(ALOAD, 0));
        il.add(new VarInsnNode(ILOAD, LV_RESUME));
        if (context.isVararg()) {
            il.add(new VarInsnNode(ALOAD, LV_VARARGS));
        }
        for (int i = 0; i < numOfRegisters(); i++) {
            il.add(new VarInsnNode(ALOAD, slotOffset() + i));
        }
        il.add(snapshotMethodInvokeInsn());

        return il;
    }

    protected InsnList resumptionHandler(LabelNode label) {
        InsnList il = new InsnList();

        il.add(label);
        il.add(ASMUtils.frameSame1(UnresolvedControlThrowable.class));

        il.add(createSnapshot());

        // register snapshot with the control exception
        il.add(new MethodInsnNode(INVOKEVIRTUAL, Type.getInternalName(UnresolvedControlThrowable.class), "resolve",
                Type.getMethodType(Type.getType(ResolvedControlThrowable.class), Type.getType(Resumable.class),
                        Type.getType(Object.class)).getDescriptor(),
                false));

        // rethrow
        il.add(new InsnNode(ATHROW));

        return il;
    }

    static class ClosureFieldInstance {

        private final FieldNode fieldNode;
        private final InsnList instantiateInsns;

        public ClosureFieldInstance(FieldNode fieldNode, InsnList instantiateInsns) {
            this.fieldNode = Objects.requireNonNull(fieldNode);
            this.instantiateInsns = Objects.requireNonNull(instantiateInsns);
        }

        public FieldNode fieldNode() {
            return fieldNode;
        }

        public InsnList instantiateInsns() {
            return instantiateInsns;
        }

    }

    public List<ClosureFieldInstance> closureFields() {
        return closureFields;
    }

    abstract static class ConstFieldInstance {

        private final Object value;
        private final String fieldName;
        private final Type ownerClassType;
        private final Type fieldType;

        public ConstFieldInstance(Object value, String fieldName, Type ownerClassType, Type fieldType) {
            this.value = Objects.requireNonNull(value);
            this.fieldName = Objects.requireNonNull(fieldName);
            this.ownerClassType = Objects.requireNonNull(ownerClassType);
            this.fieldType = Objects.requireNonNull(fieldType);
        }

        public Object value() {
            return value;
        }

        public FieldNode fieldNode() {
            return new FieldNode(ACC_PRIVATE + ACC_STATIC + ACC_FINAL, fieldName, fieldType.getDescriptor(), null,
                    null);
        }

        public abstract void doInstantiate(InsnList il);

        public InsnList instantiateInsns() {
            InsnList il = new InsnList();
            doInstantiate(il);
            il.add(new FieldInsnNode(PUTSTATIC, ownerClassType.getInternalName(), fieldName,
                    fieldType.getDescriptor()));
            return il;
        }

        public InsnList accessInsns() {
            InsnList il = new InsnList();
            il.add(new FieldInsnNode(GETSTATIC, ownerClassType.getInternalName(), fieldName,
                    fieldType.getDescriptor()));
            return il;
        }

    }

    public List<ConstFieldInstance> constFields() {
        return constFields;
    }

    private List<LocalVariableNode> baseLocals(LabelNode l_begin, LabelNode l_end) {
        List<LocalVariableNode> locals = new ArrayList<>();

        locals.add(new LocalVariableNode("this", context.thisClassType().getDescriptor(), null, l_begin, l_end, 0));
        locals.add(new LocalVariableNode("context", Type.getDescriptor(ExecutionContext.class), null, l_begin,
                l_end, LV_CONTEXT));
        locals.add(new LocalVariableNode("rp", Type.INT_TYPE.getDescriptor(), null, l_begin, l_end, LV_RESUME));

        if (context.isVararg()) {
            locals.add(new LocalVariableNode("varargs", ASMUtils.arrayTypeFor(Object.class).getDescriptor(), null,
                    l_begin, l_end, LV_VARARGS));
        }

        for (int i = 0; i < numOfRegisters(); i++) {
            locals.add(new LocalVariableNode("s_" + i, Type.getDescriptor(Object.class), null, l_begin, l_end,
                    slotOffset() + i));
        }

        return locals;
    }

    private void addLocals(MethodNode node, LabelNode l_begin, LabelNode l_end, BytecodeEmitVisitor visitor) {
        List<LocalVariableNode> locals = node.localVariables;
        locals.addAll(baseLocals(l_begin, l_end));
        locals.addAll(visitor.locals());
    }

    private MethodNode emitRunMethod(String methodName, Type returnType, BytecodeEmitVisitor visitor,
            List<BasicBlock> blocks, boolean sub) {
        MethodNode node = new MethodNode(ACC_PRIVATE, methodName, methodType(returnType).getDescriptor(), null,
                throwsExceptions());

        InsnList insns = node.instructions;

        LabelNode l_begin = new LabelNode();
        LabelNode l_end = new LabelNode();

        visitor.visitBlocks(blocks);

        InsnList prefix = new InsnList();
        InsnList suffix = new InsnList();

        final LabelNode l_head;
        final List<LabelNode> els = new ArrayList<>();
        if (sub) {
            assert (!blocks.isEmpty());
            for (int i = blocks.size() - 1; i >= 0; i--) {
                BasicBlock blk = blocks.get(i);
                LabelNode l = visitor.labels.get(blk.label());
                assert (l != null);
                els.add(l);
            }
            l_head = visitor.labels.get(blocks.get(0).label());
        } else {
            l_head = new LabelNode();
            els.add(l_head);
        }

        assert (l_head != null);

        if (visitor.isResumable()) {
            LabelNode l_error_state = new LabelNode();
            LabelNode l_handler_begin = new LabelNode();

            List<LabelNode> rls = visitor.resumptionLabels();

            assert (!rls.isEmpty() || !els.isEmpty());

            prefix.add(dispatchTable(els, rls, l_error_state));

            final LabelNode l_entry = l_head;

            if (!sub) {
                prefix.add(l_entry);
                prefix.add(ASMUtils.frameSame());
            }

            suffix.add(errorState(l_error_state));
            suffix.add(resumptionHandler(l_handler_begin));

            node.tryCatchBlocks.add(new TryCatchBlockNode(l_entry, l_error_state, l_handler_begin,
                    Type.getInternalName(UnresolvedControlThrowable.class)));
        }

        insns.add(l_begin);
        insns.add(prefix);
        insns.add(visitor.instructions());
        insns.add(suffix);
        insns.add(l_end);

        addLocals(node, l_begin, l_end, visitor);

        return node;
    }

    private MethodNode emitSingletonRunMethod(BytecodeEmitVisitor visitor, List<BasicBlock> blocks) {
        return emitRunMethod(methodName(), Type.VOID_TYPE, visitor, blocks, false);
    }

    private String subRunMethodName(int segmentIdx) {
        return "run_" + segmentIdx;
    }

    private MethodNode emitSegmentedSubRunMethod(int segmentIdx, BytecodeEmitVisitor visitor,
            List<BasicBlock> blocks) {
        return emitRunMethod(subRunMethodName(segmentIdx), context.savedStateClassType(), visitor, blocks, true);
    }

    private MethodNode emitSegmentedRunMethod(int numOfSegments) {
        MethodNode node = new MethodNode(ACC_PRIVATE, methodName(), methodType().getDescriptor(), null,
                throwsExceptions());

        InsnList il = node.instructions;

        int lvOffset = slotOffset() + numOfRegisters();

        int lv_rpp = lvOffset + 0;
        int lv_methodIdx = lvOffset + 1;
        int lv_jmpIdx = lvOffset + 2;
        int lv_stateIdx = lvOffset + 3;
        int lv_savedState = lvOffset + 4;

        LabelNode l_top = new LabelNode();
        LabelNode l_ret = new LabelNode();
        LabelNode l_end = new LabelNode();

        LabelNode l_rpp = new LabelNode();
        LabelNode l_methodIdx = new LabelNode();
        LabelNode l_jmpIdx = new LabelNode();
        LabelNode l_stateIdx = new LabelNode();
        LabelNode l_savedState = new LabelNode();

        il.add(l_top);
        il.add(new FrameNode(F_SAME, 0, null, 0, null));

        // rpp = rp & ((1 << ST_SHIFT_SEGMENT) - 1)
        il.add(new VarInsnNode(ILOAD, LV_RESUME));
        il.add(ASMUtils.loadInt((1 << ST_SHIFT_SEGMENT) - 1));
        il.add(new InsnNode(IAND));
        il.add(new VarInsnNode(ISTORE, lv_rpp));
        il.add(l_rpp);
        il.add(new FrameNode(F_APPEND, 1, new Object[] { Opcodes.INTEGER }, 0, null));

        // methodIdx = rp >>> ST_SHIFT_SEGMENT
        il.add(new VarInsnNode(ILOAD, LV_RESUME));
        il.add(ASMUtils.loadInt(ST_SHIFT_SEGMENT));
        il.add(new InsnNode(IUSHR));
        il.add(new VarInsnNode(ISTORE, lv_methodIdx));
        il.add(l_methodIdx);
        il.add(new FrameNode(F_APPEND, 1, new Object[] { Opcodes.INTEGER }, 0, null));

        // jmpIdx = rpp >>> ST_SHIFT_LABELIDX
        il.add(new VarInsnNode(ILOAD, lv_rpp));
        il.add(ASMUtils.loadInt(ST_SHIFT_LABELIDX));
        il.add(new InsnNode(IUSHR));
        il.add(new VarInsnNode(ISTORE, lv_jmpIdx));
        il.add(l_jmpIdx);
        il.add(new FrameNode(F_APPEND, 1, new Object[] { Opcodes.INTEGER }, 0, null));

        // stateIdx = (rp & ((1 << ST_SHIFT_LABELIDX) - 1)) - jmpIdx
        il.add(new VarInsnNode(ILOAD, LV_RESUME));
        il.add(ASMUtils.loadInt((1 << ST_SHIFT_LABELIDX) - 1));
        il.add(new InsnNode(IAND));
        il.add(new VarInsnNode(ILOAD, lv_jmpIdx));
        il.add(new InsnNode(ISUB));
        il.add(new VarInsnNode(ISTORE, lv_stateIdx));
        il.add(l_stateIdx);
        il.add(new FrameNode(F_APPEND, 1, new Object[] { Opcodes.INTEGER }, 0, null));

        // savedState = null
        il.add(new InsnNode(ACONST_NULL));
        il.add(new VarInsnNode(ASTORE, lv_savedState));
        il.add(l_savedState);
        il.add(new FrameNode(F_APPEND, 1, new Object[] { context.savedStateClassType().getInternalName() }, 0,
                null));

        // switch on methodIdx

        LabelNode l_after = new LabelNode();

        LabelNode l_error = new LabelNode();
        LabelNode[] l_invokes = new LabelNode[numOfSegments];
        for (int i = 0; i < numOfSegments; i++) {
            l_invokes[i] = new LabelNode();
        }

        il.add(new VarInsnNode(ILOAD, lv_methodIdx));
        il.add(new TableSwitchInsnNode(0, numOfSegments - 1, l_error, l_invokes));

        for (int i = 0; i < numOfSegments; i++) {
            il.add(l_invokes[i]);
            il.add(new FrameNode(F_SAME, 0, null, 0, null));
            // push arguments to stack
            il.add(new VarInsnNode(ALOAD, 0));
            il.add(new VarInsnNode(ALOAD, LV_CONTEXT));
            il.add(new VarInsnNode(ILOAD, lv_stateIdx)); // pass stateIdx to the sub-method
            if (context.isVararg()) {
                il.add(new VarInsnNode(ALOAD, LV_VARARGS));
            }
            for (int j = 0; j < numOfRegisters(); j++) {
                il.add(new VarInsnNode(ALOAD, slotOffset() + j));
            }

            il.add(new MethodInsnNode(INVOKESPECIAL, context.thisClassType().getInternalName(), subRunMethodName(i),
                    subMethodType().getDescriptor(), false));

            il.add(new VarInsnNode(ASTORE, lv_savedState));
            il.add(new JumpInsnNode(GOTO, l_after));
        }

        // error state
        il.add(errorState(l_error));

        il.add(l_after);
        il.add(new FrameNode(F_SAME, 0, null, 0, null));

        il.add(new VarInsnNode(ALOAD, lv_savedState));
        il.add(new JumpInsnNode(IFNULL, l_ret)); // savedState == null ?

        // continuing: savedState != null

        // FIXME: taken from ResumeMethod -- beware of code duplication!

        il.add(new VarInsnNode(ALOAD, lv_savedState)); // saved state
        il.add(new MethodInsnNode(INVOKEVIRTUAL, Type.getInternalName(DefaultSavedState.class), "resumptionPoint",
                Type.getMethodDescriptor(Type.INT_TYPE), false)); // resumption point
        il.add(new VarInsnNode(ISTORE, LV_RESUME));

        // registers
        if (context.isVararg() || numOfRegisters() > 0) {
            il.add(new VarInsnNode(ALOAD, lv_savedState));
            il.add(new MethodInsnNode(INVOKEVIRTUAL, Type.getInternalName(DefaultSavedState.class), "registers",
                    Type.getMethodDescriptor(ASMUtils.arrayTypeFor(Object.class)), false));

            int numRegs = numOfRegisters() + (context.isVararg() ? 1 : 0);

            for (int i = 0; i < numRegs; i++) {
                if (i + 1 < numRegs) {
                    il.add(new InsnNode(DUP));
                }
                il.add(ASMUtils.loadInt(i));
                il.add(new InsnNode(AALOAD));
                if (i == 0 && context.isVararg()) {
                    il.add(new TypeInsnNode(CHECKCAST, ASMUtils.arrayTypeFor(Object.class).getInternalName()));
                }
                il.add(new VarInsnNode(ASTORE, LV_VARARGS + i));
            }
        }

        // loop back to the beginning
        il.add(new JumpInsnNode(GOTO, l_top));

        // got a null, that's the end
        il.add(l_ret);
        il.add(new FrameNode(F_SAME, 0, null, 0, null));
        il.add(new InsnNode(RETURN));

        il.add(l_end);

        // add local variables
        node.localVariables.addAll(baseLocals(l_top, l_end));
        node.localVariables
                .add(new LocalVariableNode("rpp", Type.INT_TYPE.getDescriptor(), null, l_rpp, l_ret, lv_rpp));
        node.localVariables.add(new LocalVariableNode("methodIdx", Type.INT_TYPE.getDescriptor(), null, l_methodIdx,
                l_ret, lv_methodIdx));
        node.localVariables.add(
                new LocalVariableNode("jmpIdx", Type.INT_TYPE.getDescriptor(), null, l_jmpIdx, l_ret, lv_jmpIdx));
        node.localVariables.add(new LocalVariableNode("stateIdx", Type.INT_TYPE.getDescriptor(), null, l_stateIdx,
                l_ret, lv_stateIdx));
        node.localVariables.add(new LocalVariableNode("savedState", context.savedStateClassType().getDescriptor(),
                null, l_savedState, l_ret, lv_savedState));

        return node;
    }

    public List<MethodNode> methodNodes() {
        return methodNodes;
    }

}