/*
 * Copyright (c) Contributors, http://opensimulator.org/
 * See CONTRIBUTORS.TXT for a full list of copyright holders.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSimulator Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using OpenSim.Region.ScriptEngine.Shared.ScriptBase;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;


/**
 * @brief Wrapper class for ScriptMyILGen to do simple optimizations.
 *        The main one is to figure out which locals are active at the labels
 *        so the stack capture/restore code doesn't have to do everything.
 *        Second is it removes unnecessary back-to-back stloc/ldloc's.
 */

namespace OpenSim.Region.ScriptEngine.Yengine
{
    /**
     * @brief This is a list that keeps track of types pushed on the evaluation stack.
     */
    public class StackDepth: List<Type>
    {
        public List<bool> isBoxeds = new List<bool>();

        /**
         * @brief Clear both stacks.
         */
        public new void Clear()
        {
            base.Clear();
            isBoxeds.Clear();
        }

        /**
         * @brief Pop call parameters and validate the types.
         */
        public void Pop(ParameterInfo[] pis)
        {
            int n = pis.Length;
            int c = this.Count;
            if(n > c)
                throw new Exception("stack going negative");
            for(int i = n; --i >= 0;)
            {
                --c;
                ExpectedVsOnStack(pis[i].ParameterType, this[c], isBoxeds[c]);
            }
            Pop(n);
        }

        /**
         * @brief Pop values and validate the types.
         */
        public void Pop(Type[] ts)
        {
            int n = ts.Length;
            int c = this.Count;
            if(n > c)
                throw new Exception("stack going negative");
            for(int i = ts.Length; --i >= 0;)
            {
                --c;
                ExpectedVsOnStack(ts[i], this[c], isBoxeds[c]);
            }
            Pop(n);
        }

        /**
         * @brief Pop a single value and validate the type.
         */
        public void Pop(Type t)
        {
            int c = this.Count;
            if(c < 1)
                throw new Exception("stack going negative");
            ExpectedVsOnStack(t, this[c - 1], isBoxeds[c - 1]);
            Pop(1);
        }

        /**
         * @brief Pop a single value and validate that it is a numeric type.
         */
        public Type PopNumVal()
        {
            int c = this.Count;
            if(c < 1)
                throw new Exception("stack going negative");
            Type st = this[--c];
            if(st == null)
            {
                throw new Exception("stack has null, expecting a numeric");
            }
            if(isBoxeds[c])
            {
                throw new Exception("stack is boxed " + st.Name + ", expecting a numeric");
            }
            if((st != typeof(bool)) && (st != typeof(char)) && (st != typeof(int)) &&
                (st != typeof(long)) && (st != typeof(float)) && (st != typeof(double)))
            {
                throw new Exception("stack has " + st.Name + ", expecting a numeric");
            }
            return Pop(1);
        }

        /**
         * @brief Pop a single value and validate that it is a reference type
         */
        public Type PopRef()
        {
            int c = this.Count;
            if(c < 1)
                throw new Exception("stack going negative");
            Type st = this[--c];
            if((st != null) && !isBoxeds[c] && st.IsValueType)
            {
                throw new Exception("stack has " + st.Name + ", expecting a ref type");
            }
            return Pop(1);
        }

        /**
         * @brief Pop a single value and validate that it is a value type
         */
        public Type PopValue()
        {
            int c = this.Count;
            if(c < 1)
                throw new Exception("stack going negative");
            Type st = this[--c];
            if(st == null)
            {
                throw new Exception("stack has null, expecting a value type");
            }
            if(!st.IsValueType)
            {
                throw new Exception("stack has " + st.Name + ", expecting a value type");
            }
            if(isBoxeds[c])
            {
                throw new Exception("stack has boxed " + st.Name + ", expecting an unboxed value type");
            }
            return Pop(1);
        }

        // ex = what is expected to be on stack
        // st = what is actually on stack (null for ldnull)
        // stBoxed = stack value is boxed
        public static void ExpectedVsOnStack(Type ex, Type st, bool stBoxed)
        {
            // ldnull pushed on stack can go into any pointer type
            if(st == null)
            {
                if(ex.IsByRef || ex.IsPointer || ex.IsClass || ex.IsInterface)
                    return;
                throw new Exception("stack has null, expect " + ex.Name);
            }

            // simple case of expecting an object
            // ...so the stack can have object,string, etc
            // but we cant allow int = boxed int here
            if(ex.IsAssignableFrom(st) && !stBoxed)
                return;

            // case of expecting an enum on the stack
            // but all the CIL code knows about are ints etc
            // so convert the Enum type to integer or whatever
            // and that should be assignable from what's on stack
            if(ex.IsEnum && typeof(int).IsAssignableFrom(st))
                return;

            // bool, char, int are interchangeable on the stack
            if((ex == typeof(bool) || ex == typeof(char) || ex == typeof(int)) &&
                (st == typeof(bool) || st == typeof(char) || st == typeof(int)))
                return;

            // float and double are interchangeable on the stack
            if((ex == typeof(float) || ex == typeof(double)) &&
                (st == typeof(float) || st == typeof(double)))
                return;

            // object can accept any boxed type
            if((ex == typeof(object)) && stBoxed)
                return;

            // otherwise, it is disallowed
            throw new Exception("stack has " + StackTypeString(st, stBoxed) + ", expect " + ex.Name);
        }

        /**
         * @brief Pop values without any validation.
         */
        public Type Pop(int n)
        {
            if(this.Count != isBoxeds.Count)
                throw new Exception("isBoxeds count bad");
            Type lastPopped = null;
            int c = this.Count;
            if(n > c)
                throw new Exception("stack going negative");
            if(n > 0)
            {
                lastPopped = this[c - n];
                this.RemoveRange(c - n, n);
                isBoxeds.RemoveRange(c - n, n);
            }
            if(this.Count != isBoxeds.Count)
                throw new Exception("isBoxeds count bad");
            return lastPopped;
        }

        /**
         * @brief Peek at the n'th stack value.
         *        n = 0 : top of stack
         *            1 : next to top
         *                ...
         */
        public Type Peek(int n)
        {
            int c = this.Count;
            if(n > c - 1)
                throw new Exception("stack going negative");
            if(this.Count != isBoxeds.Count)
                throw new Exception("isBoxeds count bad");
            return this[c - n - 1];
        }
        public bool PeekBoxed(int n)
        {
            int c = isBoxeds.Count;
            if(n > c - 1)
                throw new Exception("stack going negative");
            if(this.Count != isBoxeds.Count)
                throw new Exception("isBoxeds count bad");
            return isBoxeds[c - n - 1];
        }

        /**
         * @brief Push a single value of the given type.
         */
        public void Push(Type t)
        {
            Push(t, false);
        }
        public void Push(Type t, bool isBoxed)
        {
            if(this.Count != isBoxeds.Count)
                throw new Exception("isBoxeds count bad");
            this.Add(t);
            isBoxeds.Add(isBoxed);
        }

        /**
         * @brief See if the types at a given label exactly match those on the stack.
         *        We should have the stack types be the same no matter how we branched 
         *        or fell through to a particular label.
         */
        public void Matches(ScriptMyLabel label)
        {
            Type[] ts = label.stackDepth;
            bool[] tsBoxeds = label.stackBoxeds;
            int i;

            if(this.Count != isBoxeds.Count)
                throw new Exception("isBoxeds count bad");

            if(ts == null)
            {
                label.stackDepth = this.ToArray();
                label.stackBoxeds = isBoxeds.ToArray();
            }
            else if(ts.Length != this.Count)
            {
                throw new Exception("stack depth mismatch");
            }
            else
            {
                for(i = this.Count; --i >= 0;)
                {
                    if(tsBoxeds[i] != this.isBoxeds[i])
                        goto mismatch;
                    if(ts[i] == this[i])
                        continue;
                    if((ts[i] == typeof(bool) || ts[i] == typeof(char) || ts[i] == typeof(int)) &&
                        (this[i] == typeof(bool) || this[i] == typeof(char) || this[i] == typeof(int)))
                        continue;
                    if((ts[i] == typeof(double) || ts[i] == typeof(float)) &&
                        (this[i] == typeof(double) || this[i] == typeof(float)))
                        continue;
                    goto mismatch;
                }
            }
            return;
            mismatch:
            throw new Exception("stack type mismatch: " + StackTypeString(ts[i], tsBoxeds[i]) + " vs " + StackTypeString(this[i], this.isBoxeds[i]));
        }

        private static string StackTypeString(Type ts, bool isBoxed)
        {
            if(!isBoxed)
                return ts.Name;
            return "[" + ts.Name + "]";
        }
    }

    /**
     * @brief One of these per opcode and label in the function plus other misc markers.
     *        They form the CIL instruction stream of the function.
     */
    public abstract class GraphNode
    {
        private static readonly bool DEBUG = false;

        public const int OPINDENT = 4;
        public const int OPDEBLEN = 12;

        public ScriptCollector coll;
        public GraphNodeBeginExceptionBlock tryBlock;  // start of enclosing try block
                                                       // valid in the try section
                                                       // null in the catch/finally sections
                                                       // null outside of try block
                                                       // for the try node itself, links to outer try block
        public GraphNodeBeginExceptionBlock excBlock;  // start of enclosing try block
                                                       // valid in the try/catch/finally sections
                                                       // null outside of try/catch/finally block
                                                       // for the try node itself, links to outer try block

        /*
         * List of nodes in order as originally given.
         */
        public GraphNode nextLin, prevLin;
        public int linSeqNo;

        /**
         * @brief Save pointer to collector.
         */
        public GraphNode(ScriptCollector coll)
        {
            this.coll = coll;
        }

        /**
         * @brief Chain graph node to end of linear list.
         */
        public virtual void ChainLin()
        {
            coll.lastLin.nextLin = this;
            this.prevLin = coll.lastLin;
            coll.lastLin = this;
            this.tryBlock = coll.curTryBlock;
            this.excBlock = coll.curExcBlock;

            if(DEBUG)
            {
                StringBuilder sb = new StringBuilder("ChainLin*:");
                sb.Append(coll.stackDepth.Count.ToString("D2"));
                sb.Append(' ');
                this.DebString(sb);
                Console.WriteLine(sb.ToString());
            }
        }

        /**
         * @brief Append full info to debugging string for printing out the instruction.
         */
        public void DebStringExt(StringBuilder sb)
        {
            int x = sb.Length;
            sb.Append(this.linSeqNo.ToString().PadLeft(5));
            sb.Append(": ");
            this.DebString(sb);

            if(this.ReadsLocal() != null)
                ScriptCollector.PadToLength(sb, x + 60, " [read]");
            if(this.WritesLocal() != null)
                ScriptCollector.PadToLength(sb, x + 68, " [write]");
            ScriptCollector.PadToLength(sb, x + 72, " ->");
            bool first = true;
            foreach(GraphNode nn in this.NextNodes)
            {
                if(first)
                {
                    sb.Append(nn.linSeqNo.ToString().PadLeft(5));
                    first = false;
                }
                else
                {
                    sb.Append(',');
                    sb.Append(nn.linSeqNo);
                }
            }
        }

        /**
         * @brief See if it's possible for it to fall through to the next inline (nextLin) instruction.
         */
        public virtual bool CanFallThrough()
        {
            return true;
        }

        /**
         * @brief Append to debugging string for printing out the instruction.
         */
        public abstract void DebString(StringBuilder sb);
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            this.DebString(sb);
            return sb.ToString();
        }

        /**
         * @brief See if this instruction reads a local variable.
         */
        public virtual ScriptMyLocal ReadsLocal()
        {
            return null;
        }

        /**
         * @brief See if this instruction writes a local variable.
         */
        public virtual ScriptMyLocal WritesLocal()
        {
            return null;
        }

        /**
         * @brief Write this instruction out to the wrapped object file.
         */
        public abstract void WriteOutOne(ScriptMyILGen ilGen);

        /**
         * @brief Iterate through all the possible next nodes, including the next inline node, if any.
         *        The next inline code is excluded if the instruction never falls through, eg, return, unconditional branch.
         *        It includes a possible conditional branch to the beginning of the corresponding catch/finally of every 
         *        instruction in a try section.
         */
        private System.Collections.Generic.IEnumerable<GraphNode> nextNodes, nextNodesCatchFinally;
        public System.Collections.Generic.IEnumerable<GraphNode> NextNodes
        {
            get
            {
                if(nextNodes == null)
                {
                    nextNodes = GetNNEnumerable();
                    nextNodesCatchFinally = new NNEnumerableCatchFinally(this);
                }
                return nextNodesCatchFinally;
            }
        }

        /**
         * @brief This acts as a wrapper around all the other NNEnumerable's below.
         *        It assumes every instruction in a try { } can throw an exception so it 
         *        says that every instruction in a try { } can conditionally branch to 
         *        the beginning of the corresponding catch { } or finally { }.
         */
        private class NNEnumerableCatchFinally: System.Collections.Generic.IEnumerable<GraphNode>
        {
            private GraphNode gn;
            public NNEnumerableCatchFinally(GraphNode gn)
            {
                this.gn = gn;
            }
            System.Collections.Generic.IEnumerator<GraphNode> System.Collections.Generic.IEnumerable<GraphNode>.GetEnumerator()
            {
                return new NNEnumeratorCatchFinally(gn);
            }
            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return new NNEnumeratorCatchFinally(gn);
            }
        }
        private class NNEnumeratorCatchFinally: NNEnumeratorBase
        {
            private GraphNode gn;
            private int index = 0;
            private System.Collections.Generic.IEnumerator<GraphNode> realEnumerator;
            public NNEnumeratorCatchFinally(GraphNode gn)
            {
                this.gn = gn;
                this.realEnumerator = gn.nextNodes.GetEnumerator();
            }
            public override bool MoveNext()
            {
                // First off, return any targets the instruction can come up with.
                if(realEnumerator.MoveNext())
                {
                    nn = realEnumerator.Current;
                    return true;
                }

                // Then if this instruction is in a try section, say this instruction 
                // can potentially branch to the beginning of the corresponding 
                // catch/finally.
                if((index == 0) && (gn.tryBlock != null))
                {
                    index++;
                    nn = gn.tryBlock.catchFinallyBlock;
                    return true;
                }

                // That's all we can do.
                nn = null;
                return false;
            }
            public override void Reset()
            {
                realEnumerator.Reset();
                index = 0;
                nn = null;
            }
        }

        /**
         * @brief This default iterator always returns the next inline node as the one-and-only next node.
         *        Other instructions need to override it if they can possibly do other than that.
         */

        /**
         * @brief GetNNEnumerable() gets the nextnode enumerable part of a GraphNode,
         *        which in turn gives the list of nodes that can possibly be next in 
         *        a flow-control sense.  It simply instantiates the NNEnumerator sub-
         *        class which does the actual enumeration.
         */
        protected virtual System.Collections.Generic.IEnumerable<GraphNode> GetNNEnumerable()
        {
            return new NNEnumerable(this, typeof(NNEnumerator));
        }

        private class NNEnumerator: NNEnumeratorBase
        {
            private GraphNode gn;
            private int index;
            public NNEnumerator(GraphNode gn)
            {
                this.gn = gn;
            }
            public override bool MoveNext()
            {
                switch(index)
                {
                    case 0:
                    {
                        index++;
                        nn = gn.nextLin;
                        return nn != null;
                    }
                    case 1:
                    {
                        nn = null;
                        return false;
                    }
                }
                throw new Exception();
            }
            public override void Reset()
            {
                index = 0;
                nn = null;
            }
        }
    }

    /**
     * @brief Things that derive from this are the beginning of a block.
     *        A block of code is that which begins with a label or is the beginning of all code
     *        and it contains no labels, ie, it can't be jumped into other than at its beginning.
     */
    public abstract class GraphNodeBlock: GraphNode
    {
        public List<ScriptMyLocal> localsWrittenBeforeRead = new List<ScriptMyLocal>();
        public List<ScriptMyLocal> localsReadBeforeWritten = new List<ScriptMyLocal>();
        public int hasBeenResolved;
        public GraphNodeBlock(ScriptCollector coll) : base(coll) { }
    }

    /**
     * @brief This placeholder is at the beginning of the code so the first few instructions 
     *        belong to some block.
     */
    public class GraphNodeBegin: GraphNodeBlock
    {
        public GraphNodeBegin(ScriptCollector coll) : base(coll) { }
        public override void DebString(StringBuilder sb)
        {
            sb.Append("begin");
        }
        public override void WriteOutOne(ScriptMyILGen ilGen)
        {
        }
    }

    /**
     * @brief Beginning of try block.
     */
    public class GraphNodeBeginExceptionBlock: GraphNodeBlock
    {
        public GraphNodeBeginExceptionBlock outerTryBlock;      // next outer try opcode or null
        public GraphNodeCatchFinallyBlock catchFinallyBlock;  // start of associated catch or finally
        public GraphNodeEndExceptionBlock endExcBlock;        // end of associated catch or finally
        public int excBlkSeqNo;                                 // debugging

        public GraphNodeBeginExceptionBlock(ScriptCollector coll) : base(coll)
        {
        }

        public override void ChainLin()
        {
            base.ChainLin();

            // we should always start try blocks with nothing on stack
            // ...as CLI wipes stack for various conditions
            if(coll.stackDepth.Count != 0)
            {
                throw new Exception("stack depth " + coll.stackDepth.Count);
            }
        }

        public override void DebString(StringBuilder sb)
        {
            sb.Append("  beginexceptionblock_");
            sb.Append(excBlkSeqNo);
        }

        public override void WriteOutOne(ScriptMyILGen ilGen)
        {
            ilGen.BeginExceptionBlock();
        }
    }

    /**
     * @brief Beginning of catch or finally block.
     */
    public abstract class GraphNodeCatchFinallyBlock: GraphNodeBlock
    {
        public GraphNodeCatchFinallyBlock(ScriptCollector coll) : base(coll)
        {
        }

        public override void ChainLin()
        {
            base.ChainLin();

            // we should always start catch/finally blocks with nothing on stack
            // ...as CLI wipes stack for various conditions
            if(coll.stackDepth.Count != 0)
            {
                throw new Exception("stack depth " + coll.stackDepth.Count);
            }
        }
    }

    /**
     * @brief Beginning of catch block.
     */
    public class GraphNodeBeginCatchBlock: GraphNodeCatchFinallyBlock
    {
        public Type excType;

        public GraphNodeBeginCatchBlock(ScriptCollector coll, Type excType) : base(coll)
        {
            this.excType = excType;
        }

        public override void ChainLin()
        {
            base.ChainLin();

            // catch block always enters with one value on stack
            if(coll.stackDepth.Count != 0)
            {
                throw new Exception("stack depth " + coll.stackDepth.Count);
            }
            coll.stackDepth.Push(excType);
        }

        public override void DebString(StringBuilder sb)
        {
            sb.Append("  begincatchblock_");
            sb.Append(excBlock.excBlkSeqNo);
        }

        public override void WriteOutOne(ScriptMyILGen ilGen)
        {
            ilGen.BeginCatchBlock(excType);
        }

        /**
         * @brief The beginning of every catch { } conditinally branches to the beginning 
         *        of all outer catch { }s up to and including the next outer finally { }.
         */
        protected override System.Collections.Generic.IEnumerable<GraphNode> GetNNEnumerable()
        {
            return new NNEnumerable(this, typeof(NNEnumerator));
        }

        private class NNEnumerator: NNEnumeratorBase
        {
            private GraphNodeBeginCatchBlock gn;
            private int index;
            public NNEnumerator(GraphNodeBeginCatchBlock gn)
            {
                this.gn = gn;
            }
            public override bool MoveNext()
            {
                while(true)
                {
                    switch(index)
                    {
                        case 0:
                        {
                            // start with the fallthru
                            nn = gn.nextLin;
                            index++;
                            return true;
                        }

                        case 1:
                        {
                            // get the first outer catch { } or finally { }
                            // pretend we last returned beginning of this catch { }
                            // then loop back to get next outer catch { } or finally { }
                            nn = gn;
                            break;
                        }

                        case 2:
                        {
                            // nn points to a catch { } previously returned
                            // get the corresponding try { }
                            GraphNodeBeginExceptionBlock nntry = nn.excBlock;

                            // step out to next outer try { }
                            nntry = nntry.excBlock;
                            if(nntry == null)
                                break;

                            // return corresponding catch { } or finally { }
                            nn = nntry.catchFinallyBlock;

                            // if it's a finally { } we don't do anything after that
                            if(nn is GraphNodeBeginFinallyBlock)
                                index++;
                            return true;
                        }

                        case 3:
                        {
                            // we've returned the fallthru, catches and one finally
                            // so there's nothing more to say
                            nn = null;
                            return false;
                        }

                        default:
                            throw new Exception();
                    }
                    index++;
                }
            }
            public override void Reset()
            {
                index = 0;
                nn = null;
            }
        }
    }

    /**
     * @brief Beginning of finally block.
     */
    public class GraphNodeBeginFinallyBlock: GraphNodeCatchFinallyBlock
    {

        // leaveTargets has a list of all the targets of any contained 
        // leave instructions, ie, where an endfinally can possibly jump.
        // But only those targets within the next outer finally { }, we 
        // don't contain any targets outside of that, those targets are 
        // stored in the actual finally that will jump to the target.
        // The endfinally enumerator assumes that it is always possible 
        // for it to jump to the next outer finally (as would happen for
        // an uncaught exception), so no need to do anything special.
        public List<GraphNodeBlock> leaveTargets = new List<GraphNodeBlock>();

        public GraphNodeBeginFinallyBlock(ScriptCollector coll) : base(coll)
        {
        }

        public override void DebString(StringBuilder sb)
        {
            sb.Append("  beginfinallyblock_");
            sb.Append(excBlock.excBlkSeqNo);
        }

        public override void WriteOutOne(ScriptMyILGen ilGen)
        {
            ilGen.BeginFinallyBlock();
        }
    }

    /**
     * @brief End of try/catch/finally block.
     */
    public class GraphNodeEndExceptionBlock: GraphNode
    {
        public GraphNodeEndExceptionBlock(ScriptCollector coll) : base(coll)
        {
        }

        public override void ChainLin()
        {
            base.ChainLin();

            // we should always end exception blocks with nothing on stack
            // ...as CLI wipes stack for various conditions
            if(coll.stackDepth.Count != 0)
            {
                throw new Exception("stack depth " + coll.stackDepth.Count);
            }
        }

        public override void DebString(StringBuilder sb)
        {
            sb.Append("  endexceptionblock_");
            sb.Append(excBlock.excBlkSeqNo);
        }

        public override void WriteOutOne(ScriptMyILGen ilGen)
        {
            ilGen.EndExceptionBlock();
        }
    }

    /**
     * @brief Actual instruction emits...
     */
    public abstract class GraphNodeEmit: GraphNode
    {
        public OpCode opcode;
        public Token errorAt;

        public GraphNodeEmit(ScriptCollector coll, Token errorAt, OpCode opcode) : base(coll)
        {
            this.opcode = opcode;
            this.errorAt = errorAt;
        }

        public override void ChainLin()
        {
            base.ChainLin();

            // compute resultant stack depth
            int stack = coll.stackDepth.Count;

            if((stack != 0) && ((opcode == OpCodes.Endfinally) || (opcode == OpCodes.Leave) || (opcode == OpCodes.Rethrow)))
            {
                throw new Exception(opcode + " stack depth " + stack);
            }
            if((stack != 1) && (opcode == OpCodes.Throw))
            {
                throw new Exception(opcode + " stack depth " + stack);
            }
        }

        /**
         * @brief See if it's possible for it to fall through to the next inline (nextLin) instruction.
         */
        public override bool CanFallThrough()
        {
            switch(opcode.FlowControl)
            {
                case FlowControl.Branch:
                    return false;  // unconditional branch
                case FlowControl.Break:
                    return true;   // break
                case FlowControl.Call:
                    return true;   // call
                case FlowControl.Cond_Branch:
                    return true;   // conditional branch
                case FlowControl.Next:
                    return true;   // falls through to next instruction
                case FlowControl.Return:
                    return false;  // return
                case FlowControl.Throw:
                    return false;  // throw
                default:
                {
                    string op = opcode.ToString();
                    if(op == "volatile.")
                        return true;
                    throw new Exception("unknown flow control " + opcode.FlowControl + " for " + op);
                }
            }
        }

        // if followed by OpCodes.Pop, it can be discarded
        public bool isPoppable
        {
            get
            {
                return
                    ((opcode.StackBehaviourPop == StackBehaviour.Pop0) &&    // ldarg,ldloc,ldsfld
                     (opcode.StackBehaviourPush == StackBehaviour.Push1)) ||
                    ((opcode.StackBehaviourPop == StackBehaviour.Pop0) &&    // ldarga,ldloca,ldc,ldsflda,...
                     (opcode.StackBehaviourPush == StackBehaviour.Pushi)) ||
                    (opcode == OpCodes.Ldnull) ||
                    (opcode == OpCodes.Ldc_R4) ||
                    (opcode == OpCodes.Ldc_R8) ||
                    (opcode == OpCodes.Ldstr) ||
                    (opcode == OpCodes.Ldc_I8) ||
                    (opcode == OpCodes.Dup);
            }
        }

        public override void DebString(StringBuilder sb)
        {
            sb.Append("".PadRight(OPINDENT));
            sb.Append(opcode.ToString().PadRight(OPDEBLEN));
        }

        /**
         * @brief If instruction is terminating, we say there is nothing following (eg, return).
         *        Otherwise, say the one-and-only next instruction is the next instruction inline.
         */
        protected override System.Collections.Generic.IEnumerable<GraphNode> GetNNEnumerable()
        {
            return new NNEnumerable(this, typeof(NNEnumerator));
        }

        private class NNEnumerator: NNEnumeratorBase
        {
            private GraphNodeEmit gn;
            private int index;
            public NNEnumerator(GraphNodeEmit gn)
            {
                this.gn = gn;
            }
            public override bool MoveNext()
            {
                switch(index)
                {
                    case 0:
                    {
                        if(gn.CanFallThrough())
                        {
                            index++;
                            nn = gn.nextLin;
                            return nn != null;
                        }
                        return false;
                    }
                    case 1:
                    {
                        nn = null;
                        return false;
                    }
                }
                throw new Exception();
            }
            public override void Reset()
            {
                index = 0;
                nn = null;
            }
        }
    }

    public class GraphNodeEmitNull: GraphNodeEmit
    {
        public GraphNodeEmitNull(ScriptCollector coll, Token errorAt, OpCode opcode) : base(coll, errorAt, opcode)
        {
        }

        public override void ChainLin()
        {
            base.ChainLin();

            switch(opcode.ToString())
            {
                case "nop":
                    break;
                case "break":
                    break;
                case "volatile.":
                    break;
                case "ldarg.0":
                    coll.stackDepth.Push(coll.wrapped.argTypes[0]);
                    break;
                case "ldarg.1":
                    coll.stackDepth.Push(coll.wrapped.argTypes[1]);
                    break;
                case "ldarg.2":
                    coll.stackDepth.Push(coll.wrapped.argTypes[2]);
                    break;
                case "ldarg.3":
                    coll.stackDepth.Push(coll.wrapped.argTypes[3]);
                    break;
                case "ldnull":
                    coll.stackDepth.Push(null);
                    break;
                case "ldc.i4.m1":
                case "ldc.i4.0":
                case "ldc.i4.1":
                case "ldc.i4.2":
                case "ldc.i4.3":
                case "ldc.i4.4":
                case "ldc.i4.5":
                case "ldc.i4.6":
                case "ldc.i4.7":
                case "ldc.i4.8":
                {
                    coll.stackDepth.Push(typeof(int));
                    break;
                }
                case "dup":
                {
                    Type t = coll.stackDepth.Peek(0);
                    bool b = coll.stackDepth.PeekBoxed(0);
                    coll.stackDepth.Push(t, b);
                    break;
                }
                case "pop":
                {
                    coll.stackDepth.Pop(1);
                    break;
                }
                case "ret":
                {
                    int sd = (coll.wrapped.retType != typeof(void)) ? 1 : 0;
                    if(coll.stackDepth.Count != sd)
                        throw new Exception("bad stack depth");
                    if(sd > 0)
                    {
                        coll.stackDepth.Pop(coll.wrapped.retType);
                    }
                    break;
                }
                case "add":
                case "sub":
                case "mul":
                case "div":
                case "div.un":
                case "rem":
                case "rem.un":
                case "and":
                case "or":
                case "xor":
                case "shl":
                case "shr":
                case "shr.un":
                case "add.ovf":
                case "add.ovf.un":
                case "mul.ovf":
                case "mul.ovf.un":
                case "sub.ovf":
                case "sub.ovf.un":
                {
                    coll.stackDepth.PopNumVal();
                    Type t = coll.stackDepth.PopNumVal();
                    coll.stackDepth.Push(t);
                    break;
                }
                case "neg":
                case "not":
                {
                    Type t = coll.stackDepth.PopNumVal();
                    coll.stackDepth.Push(t);
                    break;
                }
                case "conv.i1":
                case "conv.i2":
                case "conv.i4":
                case "conv.i8":
                case "conv.r4":
                case "conv.r8":
                case "conv.u4":
                case "conv.u8":
                case "conv.r.un":
                case "conv.ovf.i1.un":
                case "conv.ovf.i2.un":
                case "conv.ovf.i4.un":
                case "conv.ovf.i8.un":
                case "conv.ovf.u1.un":
                case "conv.ovf.u2.un":
                case "conv.ovf.u4.un":
                case "conv.ovf.u8.un":
                case "conv.ovf.i.un":
                case "conv.ovf.u.un":
                case "conv.ovf.i1":
                case "conv.ovf.u1":
                case "conv.ovf.i2":
                case "conv.ovf.u2":
                case "conv.ovf.i4":
                case "conv.ovf.u4":
                case "conv.ovf.i8":
                case "conv.ovf.u8":
                case "conv.u2":
                case "conv.u1":
                case "conv.i":
                case "conv.ovf.i":
                case "conv.ovf.u":
                case "conv.u":
                {
                    coll.stackDepth.PopNumVal();
                    coll.stackDepth.Push(ConvToType(opcode));
                    break;
                }
                case "throw":
                {
                    if(coll.stackDepth.Count != 1)
                        throw new Exception("bad stack depth " + coll.stackDepth.Count);
                    coll.stackDepth.PopRef();
                    break;
                }
                case "ldlen":
                {
                    coll.stackDepth.Pop(typeof(string));
                    coll.stackDepth.Push(typeof(int));
                    break;
                }
                case "ldelem.i1":
                case "ldelem.u1":
                case "ldelem.i2":
                case "ldelem.u2":
                case "ldelem.i4":
                case "ldelem.u4":
                case "ldelem.i8":
                case "ldelem.i":
                case "ldelem.r4":
                case "ldelem.r8":
                case "ldelem.ref":
                {
                    Type t = coll.stackDepth.Peek(1).GetElementType();
                    coll.stackDepth.Pop(typeof(int));
                    coll.stackDepth.Pop(t.MakeArrayType());
                    coll.stackDepth.Push(t);
                    break;
                }
                case "stelem.i":
                case "stelem.i1":
                case "stelem.i2":
                case "stelem.i4":
                case "stelem.i8":
                case "stelem.r4":
                case "stelem.r8":
                case "stelem.ref":
                {
                    Type t = coll.stackDepth.Peek(2).GetElementType();
                    coll.stackDepth.Pop(t);
                    coll.stackDepth.Pop(typeof(int));
                    coll.stackDepth.Pop(t.MakeArrayType());
                    break;
                }
                case "endfinally":
                case "rethrow":
                {
                    if(coll.stackDepth.Count != 0)
                        throw new Exception("bad stack depth " + coll.stackDepth.Count);
                    break;
                }
                case "ceq":
                {
                    Type t = coll.stackDepth.Pop(1);
                    if(t == null)
                    {
                        coll.stackDepth.PopRef();
                    }
                    else
                    {
                        coll.stackDepth.Pop(t);
                    }
                    coll.stackDepth.Push(typeof(int));
                    break;
                }
                case "cgt":
                case "cgt.un":
                case "clt":
                case "clt.un":
                {
                    coll.stackDepth.PopNumVal();
                    coll.stackDepth.PopNumVal();
                    coll.stackDepth.Push(typeof(int));
                    break;
                }
                case "ldind.i4":
                {
                    coll.stackDepth.Pop(typeof(int).MakeByRefType());
                    coll.stackDepth.Push(typeof(int));
                    break;
                }
                case "stind.i4":
                {
                    coll.stackDepth.Pop(typeof(int));
                    coll.stackDepth.Pop(typeof(int).MakeByRefType());
                    break;
                }
                default:
                    throw new Exception("unknown opcode " + opcode.ToString());
            }
        }

        private static Type ConvToType(OpCode opcode)
        {
            string s = opcode.ToString();
            s = s.Substring(5);  // strip off "conv."
            if(s.StartsWith("ovf."))
                s = s.Substring(4);
            if(s.EndsWith(".un"))
                s = s.Substring(0, s.Length - 3);

            switch(s)
            {
                case "i":
                    return typeof(IntPtr);
                case "i1":
                    return typeof(sbyte);
                case "i2":
                    return typeof(short);
                case "i4":
                    return typeof(int);
                case "i8":
                    return typeof(long);
                case "r":
                case "r4":
                    return typeof(float);
                case "r8":
                    return typeof(double);
                case "u1":
                    return typeof(byte);
                case "u2":
                    return typeof(ushort);
                case "u4":
                    return typeof(uint);
                case "u8":
                    return typeof(ulong);
                case "u":
                    return typeof(UIntPtr);
                default:
                    throw new Exception("unknown opcode " + opcode.ToString());
            }
        }

        public override void WriteOutOne(ScriptMyILGen ilGen)
        {
            ilGen.Emit(errorAt, opcode);
        }
    }

    public class GraphNodeEmitNullEndfinally: GraphNodeEmitNull
    {
        public GraphNodeEmitNullEndfinally(ScriptCollector coll, Token errorAt) : base(coll, errorAt, OpCodes.Endfinally)
        {
        }

        /**
         * @brief Endfinally can branch to:
         *          1) the corresponding EndExceptionBlock
         *          2) any of the corresponding BeginFinallyBlock's leaveTargets
         *          3) the next outer BeginFinallyBlock
         */
        protected override System.Collections.Generic.IEnumerable<GraphNode> GetNNEnumerable()
        {
            return new NNEnumerable(this, typeof(NNEnumerator));
        }

        private class NNEnumerator: NNEnumeratorBase
        {
            private GraphNodeEmitNullEndfinally gn;
            private IEnumerator<GraphNodeBlock> leaveTargetEnumerator;
            private int index;
            public NNEnumerator(GraphNodeEmitNullEndfinally gn)
            {
                this.gn = gn;

                // endfinally instruction must be within some try/catch/finally mess
                GraphNodeBeginExceptionBlock thistry = gn.excBlock;

                // endfinally instruction must be within some finally { } mess
                GraphNodeBeginFinallyBlock thisfin = (GraphNodeBeginFinallyBlock)thistry.catchFinallyBlock;

                // get the list of the finally { } leave instruction targets
                this.leaveTargetEnumerator = thisfin.leaveTargets.GetEnumerator();
            }
            public override bool MoveNext()
            {
                while(true)
                {
                    switch(index)
                    {

                        // to start, return end of our finally { }
                        case 0:
                        {
                            GraphNodeBeginExceptionBlock thistry = gn.excBlock;
                            nn = thistry.endExcBlock;
                            if(nn == null)
                                throw new NullReferenceException("thistry.endExcBlock");
                            index++;
                            return true;
                        }

                        // return next one of our finally { }'s leave targets
                        // ie, where any leave instructions in the try { } want 
                        // the finally { } to go to when it finishes
                        case 1:
                        {
                            if(this.leaveTargetEnumerator.MoveNext())
                            {
                                nn = this.leaveTargetEnumerator.Current;
                                if(nn == null)
                                    throw new NullReferenceException("this.leaveTargetEnumerator.Current");
                                return true;
                            }
                            break;
                        }

                        // return beginning of next outer finally { }
                        case 2:
                        {
                            GraphNodeBeginExceptionBlock nntry = gn.excBlock;
                            while((nntry = nntry.excBlock) != null)
                            {
                                if(nntry.catchFinallyBlock is GraphNodeBeginFinallyBlock)
                                {
                                    nn = nntry.catchFinallyBlock;
                                    if(nn == null)
                                        throw new NullReferenceException("nntry.catchFinallyBlock");
                                    index++;
                                    return true;
                                }
                            }
                            break;
                        }

                        // got nothing more
                        case 3:
                        {
                            return false;
                        }

                        default:
                            throw new Exception();
                    }
                    index++;
                }
            }
            public override void Reset()
            {
                leaveTargetEnumerator.Reset();
                index = 0;
                nn = null;
            }
        }
    }

    public class GraphNodeEmitField: GraphNodeEmit
    {
        public FieldInfo field;

        public GraphNodeEmitField(ScriptCollector coll, Token errorAt, OpCode opcode, FieldInfo field) : base(coll, errorAt, opcode)
        {
            this.field = field;
        }

        public override void ChainLin()
        {
            base.ChainLin();

            switch(opcode.ToString())
            {
                case "ldfld":
                    PopPointer();
                    coll.stackDepth.Push(field.FieldType);
                    break;
                case "ldflda":
                    PopPointer();
                    coll.stackDepth.Push(field.FieldType.MakeByRefType());
                    break;
                case "stfld":
                    coll.stackDepth.Pop(field.FieldType);
                    PopPointer();
                    break;
                case "ldsfld":
                    coll.stackDepth.Push(field.FieldType);
                    break;
                case "ldsflda":
                    coll.stackDepth.Push(field.FieldType.MakeByRefType());
                    break;
                case "stsfld":
                    coll.stackDepth.Pop(field.FieldType);
                    break;
                default:
                    throw new Exception("unknown opcode " + opcode.ToString());
            }
        }
        private void PopPointer()
        {
            Type t = field.DeclaringType;               // get class/field type
            if(t.IsValueType)
            {
                Type brt = t.MakeByRefType();      // if value type, eg Vector, it can be pushed by reference or by value
                int c = coll.stackDepth.Count;
                if((c > 0) && (coll.stackDepth[c - 1] == brt))
                    t = brt;
            }
            coll.stackDepth.Pop(t);                    // type of what should be on the stack pointing to object or struct
        }

        public override void DebString(StringBuilder sb)
        {
            base.DebString(sb);
            sb.Append(field.Name);
        }

        public override void WriteOutOne(ScriptMyILGen ilGen)
        {
            ilGen.Emit(errorAt, opcode, field);
        }
    }

    public class GraphNodeEmitLocal: GraphNodeEmit
    {
        public ScriptMyLocal myLocal;

        public GraphNodeEmitLocal(ScriptCollector coll, Token errorAt, OpCode opcode, ScriptMyLocal myLocal) : base(coll, errorAt, opcode)
        {
            this.myLocal = myLocal;
        }

        public override void ChainLin()
        {
            base.ChainLin();

            switch(opcode.ToString())
            {
                case "ldloc":
                    coll.stackDepth.Push(myLocal.type);
                    break;
                case "ldloca":
                    coll.stackDepth.Push(myLocal.type.MakeByRefType());
                    break;
                case "stloc":
                    coll.stackDepth.Pop(myLocal.type);
                    break;
                default:
                    throw new Exception("unknown opcode " + opcode.ToString());
            }
        }

        public override void DebString(StringBuilder sb)
        {
            base.DebString(sb);
            sb.Append(myLocal.name);
        }

        public override ScriptMyLocal ReadsLocal()
        {
            if(opcode == OpCodes.Ldloc)
                return myLocal;
            if(opcode == OpCodes.Ldloca)
                return myLocal;
            if(opcode == OpCodes.Stloc)
                return null;
            throw new Exception("unknown opcode " + opcode);
        }
        public override ScriptMyLocal WritesLocal()
        {
            if(opcode == OpCodes.Ldloc)
                return null;
            if(opcode == OpCodes.Ldloca)
                return myLocal;
            if(opcode == OpCodes.Stloc)
                return myLocal;
            throw new Exception("unknown opcode " + opcode);
        }

        public override void WriteOutOne(ScriptMyILGen ilGen)
        {
            ilGen.Emit(errorAt, opcode, myLocal);
        }
    }

    public class GraphNodeEmitType: GraphNodeEmit
    {
        public Type type;

        public GraphNodeEmitType(ScriptCollector coll, Token errorAt, OpCode opcode, Type type) : base(coll, errorAt, opcode)
        {
            this.type = type;
        }

        public override void ChainLin()
        {
            base.ChainLin();

            switch(opcode.ToString())
            {
                case "castclass":
                case "isinst":
                {
                    coll.stackDepth.PopRef();
                    coll.stackDepth.Push(type, type.IsValueType);
                    break;
                }
                case "box":
                {
                    if(!type.IsValueType)
                        throw new Exception("can't box a non-value type");
                    coll.stackDepth.Pop(type);
                    coll.stackDepth.Push(type, true);
                    break;
                }
                case "unbox":
                case "unbox.any":
                {
                    if(!type.IsValueType)
                        throw new Exception("can't unbox to a non-value type");
                    coll.stackDepth.PopRef();
                    coll.stackDepth.Push(type);
                    break;
                }
                case "newarr":
                {
                    coll.stackDepth.Pop(typeof(int));
                    coll.stackDepth.Push(type.MakeArrayType());
                    break;
                }
                case "sizeof":
                {
                    coll.stackDepth.Pop(1);
                    coll.stackDepth.Push(typeof(int));
                    break;
                }
                case "ldelem":
                {
                    coll.stackDepth.Pop(typeof(int));
                    coll.stackDepth.Pop(type.MakeArrayType());
                    coll.stackDepth.Push(type);
                    break;
                }
                case "ldelema":
                {
                    coll.stackDepth.Pop(typeof(int));
                    coll.stackDepth.Pop(type.MakeArrayType());
                    coll.stackDepth.Push(type.MakeByRefType());
                    break;
                }
                case "stelem":
                {
                    coll.stackDepth.Pop(type);
                    coll.stackDepth.Pop(typeof(int));
                    coll.stackDepth.Pop(type.MakeArrayType());
                    break;
                }
                default:
                    throw new Exception("unknown opcode " + opcode.ToString());
            }
        }

        public override void DebString(StringBuilder sb)
        {
            base.DebString(sb);
            sb.Append(type.Name);
        }

        public override void WriteOutOne(ScriptMyILGen ilGen)
        {
            ilGen.Emit(errorAt, opcode, type);
        }
    }

    public class GraphNodeEmitLabel: GraphNodeEmit
    {
        public ScriptMyLabel myLabel;

        public GraphNodeEmitLabel(ScriptCollector coll, Token errorAt, OpCode opcode, ScriptMyLabel myLabel) : base(coll, errorAt, opcode)
        {
            this.myLabel = myLabel;
        }

        public override void ChainLin()
        {
            base.ChainLin();

            switch(opcode.ToString())
            {
                case "brfalse.s":
                case "brtrue.s":
                case "brfalse":
                case "brtrue":
                {
                    coll.stackDepth.Pop(1);
                    break;
                }
                case "beq.s":
                case "bge.s":
                case "bgt.s":
                case "ble.s":
                case "blt.s":
                case "bne.un.s":
                case "bge.un.s":
                case "bgt.un.s":
                case "ble.un.s":
                case "blt.un.s":
                case "beq":
                case "bge":
                case "bgt":
                case "ble":
                case "blt":
                case "bne.un":
                case "bge.un":
                case "bgt.un":
                case "ble.un":
                case "blt.un":
                {
                    coll.stackDepth.PopNumVal();
                    coll.stackDepth.PopNumVal();
                    break;
                }
                case "br":
                case "br.s":
                    break;
                case "leave":
                {
                    if(coll.stackDepth.Count != 0)
                        throw new Exception("bad stack depth " + coll.stackDepth.Count);
                    break;
                }
                default:
                    throw new Exception("unknown opcode " + opcode.ToString());
            }

            // if a target doesn't have a depth yet, set its depth to the depth after instruction executes
            // otherwise, make sure it matches all other branches to that target and what fell through to it
            coll.stackDepth.Matches(myLabel);
        }

        public override void DebString(StringBuilder sb)
        {
            base.DebString(sb);
            sb.Append(myLabel.name);
        }

        public override void WriteOutOne(ScriptMyILGen ilGen)
        {
            ilGen.Emit(errorAt, opcode, myLabel);
        }

        /**
         * @brief Conditional branches return the next inline followed by the branch target
         *        Unconditional branches return only the branch target
         *        But if the target is outside our scope (eg __retlbl), omit it from the list
         */
        protected override System.Collections.Generic.IEnumerable<GraphNode> GetNNEnumerable()
        {
            return new NNEnumerable(this, typeof(NNEnumerator));
        }

        private class NNEnumerator: NNEnumeratorBase
        {
            private GraphNodeEmitLabel gn;
            private int index;
            public NNEnumerator(GraphNodeEmitLabel gn)
            {
                this.gn = gn;
            }
            public override bool MoveNext()
            {
                switch(gn.opcode.FlowControl)
                {
                    case FlowControl.Branch:
                    {
                        // unconditional branch just goes to target and nothing else
                        switch(index)
                        {
                            case 0:
                            {
                                nn = gn.myLabel.whereAmI;
                                index++;
                                return nn != null;
                            }
                            case 1:
                            {
                                return false;
                            }
                        }
                        throw new Exception();
                    }
                    case FlowControl.Cond_Branch:
                    {
                        // conditional branch goes inline and to target
                        switch(index)
                        {
                            case 0:
                            {
                                nn = gn.nextLin;
                                index++;
                                return true;
                            }
                            case 1:
                            {
                                nn = gn.myLabel.whereAmI;
                                index++;
                                return nn != null;
                            }
                            case 2:
                            {
                                return false;
                            }
                        }
                        throw new Exception();
                    }
                    default:
                        throw new Exception("unknown flow control " + gn.opcode.FlowControl.ToString() +
                                             " of " + gn.opcode.ToString());
                }
            }
            public override void Reset()
            {
                index = 0;
                nn = null;
            }
        }
    }

    public class GraphNodeEmitLabelLeave: GraphNodeEmitLabel
    {
        public GraphNodeBlock unwindTo;  // if unwinding, innermost finally block being unwound
                                         //         else, same as myTarget.whereAmI
                                         // null if unwinding completely out of scope, eg, __retlbl

        public GraphNodeEmitLabelLeave(ScriptCollector coll, Token errorAt, ScriptMyLabel myLabel) : base(coll, errorAt, OpCodes.Leave, myLabel)
        {
        }

        /**
         * @brief Leave instructions have exactly one unconditional next node.
         *        Either the given target if within the same try block 
         *        or the beginning of the intervening finally block.
         */
        protected override System.Collections.Generic.IEnumerable<GraphNode> GetNNEnumerable()
        {
            return new NNEnumerable(this, typeof(NNEnumerator));
        }

        private class NNEnumerator: NNEnumeratorBase
        {
            private GraphNodeEmitLabelLeave gn;
            private int index;
            public NNEnumerator(GraphNodeEmitLabelLeave gn)
            {
                this.gn = gn;
            }
            public override bool MoveNext()
            {
                if(index == 0)
                {
                    nn = gn.unwindTo;
                    index++;
                    return nn != null;
                }
                nn = null;
                return false;
            }
            public override void Reset()
            {
                index = 0;
                nn = null;
            }
        }
    }

    public class GraphNodeEmitLabels: GraphNodeEmit
    {
        public ScriptMyLabel[] myLabels;

        public GraphNodeEmitLabels(ScriptCollector coll, Token errorAt, OpCode opcode, ScriptMyLabel[] myLabels) : base(coll, errorAt, opcode)
        {
            this.myLabels = myLabels;
        }

        public override void ChainLin()
        {
            base.ChainLin();

            switch(opcode.ToString())
            {
                case "switch":
                {
                    coll.stackDepth.Pop(typeof(int));
                    break;
                }
                default:
                    throw new Exception("unknown opcode " + opcode.ToString());
            }

            // if a target doesn't have a depth yet, set its depth to the depth after instruction executes
            // otherwise, make sure it matches all other branches to that target and what fell through to it
            foreach(ScriptMyLabel myLabel in myLabels)
            {
                coll.stackDepth.Matches(myLabel);
            }
        }

        public override void DebString(StringBuilder sb)
        {
            base.DebString(sb);
            bool first = true;
            foreach(ScriptMyLabel lbl in myLabels)
            {
                if(!first)
                    sb.Append(',');
                sb.Append(lbl.name);
                first = false;
            }
        }

        public override void WriteOutOne(ScriptMyILGen ilGen)
        {
            ilGen.Emit(errorAt, opcode, myLabels);
        }

        /**
         * @brief Return list of all labels followed by the next linear instruction
         *        But if the target is outside our scope (eg __retlbl), omit it from the list
         */
        protected override System.Collections.Generic.IEnumerable<GraphNode> GetNNEnumerable()
        {
            return new NNEnumerable(this, typeof(NNEnumerator));
        }

        private class NNEnumerator: NNEnumeratorBase
        {
            private GraphNodeEmitLabels gn;
            private int index;
            public NNEnumerator(GraphNodeEmitLabels gn)
            {
                this.gn = gn;
            }
            public override bool MoveNext()
            {
                // Return next from list of switch case labels.
                while(index < gn.myLabels.Length)
                {
                    nn = gn.myLabels[index++].whereAmI;
                    if(nn != null)
                        return true;
                }

                // If all ran out, the switch instruction falls through.
                if(index == gn.myLabels.Length)
                {
                    index++;
                    nn = gn.nextLin;
                    return true;
                }

                // Even ran out of that, say there's nothing more.
                nn = null;
                return false;
            }
            public override void Reset()
            {
                index = 0;
                nn = null;
            }
        }
    }

    public class GraphNodeEmitIntMeth: GraphNodeEmit
    {
        public ScriptObjWriter method;

        public GraphNodeEmitIntMeth(ScriptCollector coll, Token errorAt, OpCode opcode, ScriptObjWriter method) : base(coll, errorAt, opcode)
        {
            this.method = method;
        }

        public override void ChainLin()
        {
            base.ChainLin();

            switch(opcode.ToString())
            {
                case "call":
                {

                    // calls have Varpop so pop the number of arguments
                    // they are all static so there is no separate 'this' parameter
                    coll.stackDepth.Pop(this.method.argTypes);

                    // calls are also Varpush so they push a return value iff non-void
                    if(this.method.retType != typeof(void))
                        coll.stackDepth.Push(this.method.retType);
                    break;
                }

                default:
                    throw new Exception("unknown opcode " + opcode.ToString());
            }
        }

        public override void DebString(StringBuilder sb)
        {
            base.DebString(sb);
            sb.Append(method.methName);
        }

        public override void WriteOutOne(ScriptMyILGen ilGen)
        {
            ilGen.Emit(errorAt, opcode, method);
        }
    }

    public class GraphNodeEmitExtMeth: GraphNodeEmit
    {
        public MethodInfo method;

        public GraphNodeEmitExtMeth(ScriptCollector coll, Token errorAt, OpCode opcode, MethodInfo method) : base(coll, errorAt, opcode)
        {
            this.method = method;
        }

        public override void ChainLin()
        {
            base.ChainLin();

            switch(opcode.ToString())
            {
                case "call":
                case "callvirt":
                {

                    // calls have Varpop so pop the number of arguments
                    coll.stackDepth.Pop(this.method.GetParameters());
                    if((this.method.CallingConvention & CallingConventions.HasThis) != 0)
                    {
                        coll.stackDepth.Pop(method.DeclaringType);
                    }

                    // calls are also Varpush so they push a return value iff non-void
                    if(this.method.ReturnType != typeof(void))
                        coll.stackDepth.Push(this.method.ReturnType);
                    break;
                }

                default:
                    throw new Exception("unknown opcode " + opcode.ToString());
            }
        }

        public override void DebString(StringBuilder sb)
        {
            base.DebString(sb);
            sb.Append(method.Name);
        }

        public override void WriteOutOne(ScriptMyILGen ilGen)
        {
            ilGen.Emit(errorAt, opcode, method);
        }
    }

    public class GraphNodeEmitCtor: GraphNodeEmit
    {
        public ConstructorInfo ctor;

        public GraphNodeEmitCtor(ScriptCollector coll, Token errorAt, OpCode opcode, ConstructorInfo ctor) : base(coll, errorAt, opcode)
        {
            this.ctor = ctor;
        }

        public override void ChainLin()
        {
            base.ChainLin();

            switch(opcode.ToString())
            {
                case "newobj":
                {
                    coll.stackDepth.Pop(ctor.GetParameters());
                    coll.stackDepth.Push(ctor.DeclaringType);
                    break;
                }

                default:
                    throw new Exception("unknown opcode " + opcode.ToString());
            }
        }

        public override void DebString(StringBuilder sb)
        {
            base.DebString(sb);
            sb.Append(ctor.ReflectedType.Name);
        }

        public override void WriteOutOne(ScriptMyILGen ilGen)
        {
            ilGen.Emit(errorAt, opcode, ctor);
        }
    }

    public class GraphNodeEmitDouble: GraphNodeEmit
    {
        public double value;

        public GraphNodeEmitDouble(ScriptCollector coll, Token errorAt, OpCode opcode, double value) : base(coll, errorAt, opcode)
        {
            this.value = value;
        }

        public override void ChainLin()
        {
            base.ChainLin();

            switch(opcode.ToString())
            {
                case "ldc.r8":
                    coll.stackDepth.Push(typeof(double));
                    break;
                default:
                    throw new Exception("unknown opcode " + opcode.ToString());
            }
        }

        public override void DebString(StringBuilder sb)
        {
            base.DebString(sb);
            sb.Append(value);
        }

        public override void WriteOutOne(ScriptMyILGen ilGen)
        {
            ilGen.Emit(errorAt, opcode, value);
        }
    }

    public class GraphNodeEmitFloat: GraphNodeEmit
    {
        public float value;

        public GraphNodeEmitFloat(ScriptCollector coll, Token errorAt, OpCode opcode, float value) : base(coll, errorAt, opcode)
        {
            this.value = value;
        }

        public override void ChainLin()
        {
            base.ChainLin();

            switch(opcode.ToString())
            {
                case "ldc.r4":
                    coll.stackDepth.Push(typeof(float));
                    break;
                default:
                    throw new Exception("unknown opcode " + opcode.ToString());
            }
        }

        public override void DebString(StringBuilder sb)
        {
            base.DebString(sb);
            sb.Append(value);
        }

        public override void WriteOutOne(ScriptMyILGen ilGen)
        {
            ilGen.Emit(errorAt, opcode, value);
        }
    }

    public class GraphNodeEmitInt: GraphNodeEmit
    {
        public int value;

        public GraphNodeEmitInt(ScriptCollector coll, Token errorAt, OpCode opcode, int value) : base(coll, errorAt, opcode)
        {
            this.value = value;
        }

        public override void ChainLin()
        {
            base.ChainLin();

            switch(opcode.ToString())
            {
                case "ldarg":
                case "ldarg.s":
                    coll.stackDepth.Push(coll.wrapped.argTypes[value]);
                    break;
                case "ldarga":
                case "ldarga.s":
                    coll.stackDepth.Push(coll.wrapped.argTypes[value].MakeByRefType());
                    break;
                case "starg":
                case "starg.s":
                    coll.stackDepth.Pop(coll.wrapped.argTypes[value]);
                    break;
                case "ldc.i4":
                case "ldc.i4.s":
                    coll.stackDepth.Push(typeof(int));
                    break;
                default:
                    throw new Exception("unknown opcode " + opcode.ToString());
            }
        }

        public override void DebString(StringBuilder sb)
        {
            base.DebString(sb);
            sb.Append(value);
        }

        public override void WriteOutOne(ScriptMyILGen ilGen)
        {
            ilGen.Emit(errorAt, opcode, value);
        }
    }

    public class GraphNodeEmitString: GraphNodeEmit
    {
        public string value;

        public GraphNodeEmitString(ScriptCollector coll, Token errorAt, OpCode opcode, string value) : base(coll, errorAt, opcode)
        {
            this.value = value;
        }

        public override void ChainLin()
        {
            base.ChainLin();

            switch(opcode.ToString())
            {
                case "ldstr":
                    coll.stackDepth.Push(typeof(string));
                    break;
                default:
                    throw new Exception("unknown opcode " + opcode.ToString());
            }
        }

        public override void DebString(StringBuilder sb)
        {
            base.DebString(sb);
            sb.Append("\"");
            sb.Append(value);
            sb.Append("\"");
        }

        public override void WriteOutOne(ScriptMyILGen ilGen)
        {
            ilGen.Emit(errorAt, opcode, value);
        }
    }

    public class GraphNodeMarkLabel: GraphNodeBlock
    {
        public ScriptMyLabel myLabel;

        public GraphNodeMarkLabel(ScriptCollector coll, ScriptMyLabel myLabel) : base(coll)
        {
            this.myLabel = myLabel;
        }

        public override void ChainLin()
        {
            base.ChainLin();

            // if previous instruction can fall through to this label,
            //     if the label doesn't yet have a stack depth, mark it with current stack depth
            //     else, the label's stack depth from forward branches and current stack depth must match
            // else,
            //     label must have had a forward branch to it so we can know stack depth
            //     set the current stack depth to the label's stack depth as of that forward branch
            if(myLabel.whereAmI.prevLin.CanFallThrough())
            {
                coll.stackDepth.Matches(myLabel);
            }
            else
            {
                if(myLabel.stackDepth == null)
                {
                    throw new Exception("stack depth unknown at " + myLabel.name);
                }
                coll.stackDepth.Clear();
                int n = myLabel.stackDepth.Length;
                for(int i = 0; i < n; i++)
                {
                    coll.stackDepth.Push(myLabel.stackDepth[i], myLabel.stackBoxeds[i]);
                }
            }
        }

        public override void DebString(StringBuilder sb)
        {
            sb.Append(myLabel.name);
            sb.Append(':');
            if(myLabel.stackDepth != null)
            {
                sb.Append("  [");
                sb.Append(myLabel.stackDepth.Length);
                sb.Append(']');
            }
        }

        public override void WriteOutOne(ScriptMyILGen ilGen)
        {
            ilGen.MarkLabel(myLabel);
        }
    }


    /**
     * @brief Generates enumerator that steps through list of nodes that can
     *        possibly be next in a flow-control sense.
     */
    public class NNEnumerable: System.Collections.Generic.IEnumerable<GraphNode>
    {
        private object[] cps;
        private ConstructorInfo ci;

        public NNEnumerable(GraphNode gn, Type nnEnumeratorType)
        {
            this.cps = new object[] { gn };
            this.ci = nnEnumeratorType.GetConstructor(new Type[] { gn.GetType() });
        }
        System.Collections.Generic.IEnumerator<GraphNode> System.Collections.Generic.IEnumerable<GraphNode>.GetEnumerator()
        {
            return (System.Collections.Generic.IEnumerator<GraphNode>)ci.Invoke(cps);
        }
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return (System.Collections.IEnumerator)ci.Invoke(cps);
        }
    }


    /**
     * @brief Steps through list of nodes that can possible be next in a flow-control sense.
     */
    public abstract class NNEnumeratorBase: System.Collections.Generic.IEnumerator<GraphNode>
    {
        protected GraphNode nn;

        public abstract bool MoveNext();
        public abstract void Reset();

        GraphNode System.Collections.Generic.IEnumerator<GraphNode>.Current
        {
            get
            {
                return this.nn;
            }
        }
        object System.Collections.IEnumerator.Current
        {
            get
            {
                return this.nn;
            }
        }
        void System.IDisposable.Dispose()
        {
        }
    }


    public class ScriptCollector: ScriptMyILGen
    {
        public static readonly bool DEBUG = false;

        public ScriptObjWriter wrapped;
        public GraphNode firstLin, lastLin;
        private bool resolvedSomething;
        private int resolveSequence;
        private int excBlkSeqNos;
        public StackDepth stackDepth = new StackDepth();

        public GraphNodeBeginExceptionBlock curTryBlock = null;  // pushed at beginning of try
                                                                 // popped at BEGINNING of catch/finally
        public GraphNodeBeginExceptionBlock curExcBlock = null;  // pushed at beginning of try
                                                                 // popped at END of catch/finally

        private List<ScriptMyLocal> declaredLocals = new List<ScriptMyLocal>();
        private List<ScriptMyLabel> definedLabels = new List<ScriptMyLabel>();

        public string methName
        {
            get
            {
                return wrapped.methName;
            }
        }

        /**
         * @brief Wrap the optimizer around the ScriptObjWriter to collect the instruction stream.
         *        All stream-writing calls get saved to our graph nodes instead of being written to object file.
         */
        public ScriptCollector(ScriptObjWriter wrapped)
        {
            this.wrapped = wrapped;
            GraphNodeBegin gnb = new GraphNodeBegin(this);
            this.firstLin = gnb;
            this.lastLin = gnb;
        }

        public ScriptMyLocal DeclareLocal(Type type, string name)
        {
            ScriptMyLocal loc = new ScriptMyLocal();
            loc.name = name;
            loc.type = type;
            loc.number = wrapped.localNumber++;
            declaredLocals.Add(loc);
            return loc;
        }

        public ScriptMyLabel DefineLabel(string name)
        {
            ScriptMyLabel lbl = new ScriptMyLabel();
            lbl.name = name;
            lbl.number = wrapped.labelNumber++;
            definedLabels.Add(lbl);
            return lbl;
        }

        public void BeginExceptionBlock()
        {
            GraphNodeBeginExceptionBlock tryBlock = new GraphNodeBeginExceptionBlock(this);
            tryBlock.ChainLin();
            tryBlock.excBlkSeqNo = ++this.excBlkSeqNos;
            this.curExcBlock = tryBlock;
            this.curTryBlock = tryBlock;
        }

        public void BeginCatchBlock(Type excType)
        {
            GraphNodeBeginCatchBlock catchBlock = new GraphNodeBeginCatchBlock(this, excType);
            catchBlock.ChainLin();
            if(curExcBlock.catchFinallyBlock != null)
                throw new Exception("only one catch/finally allowed per try");
            curExcBlock.catchFinallyBlock = catchBlock;
            curTryBlock = curExcBlock.tryBlock;
        }

        public void BeginFinallyBlock()
        {
            GraphNodeBeginFinallyBlock finallyBlock = new GraphNodeBeginFinallyBlock(this);
            finallyBlock.ChainLin();
            if(curExcBlock.catchFinallyBlock != null)
                throw new Exception("only one catch/finally allowed per try");
            curExcBlock.catchFinallyBlock = finallyBlock;
            curTryBlock = curExcBlock.tryBlock;
        }

        public void EndExceptionBlock()
        {
            GraphNodeEndExceptionBlock endExcBlock = new GraphNodeEndExceptionBlock(this);
            endExcBlock.ChainLin();
            curExcBlock.endExcBlock = endExcBlock;
            curTryBlock = curExcBlock.tryBlock;
            curExcBlock = curExcBlock.excBlock;
        }

        public void Emit(Token errorAt, OpCode opcode)
        {
            if(opcode == OpCodes.Endfinally)
            {
                new GraphNodeEmitNullEndfinally(this, errorAt).ChainLin();
            }
            else
            {
                new GraphNodeEmitNull(this, errorAt, opcode).ChainLin();
            }
        }

        public void Emit(Token errorAt, OpCode opcode, FieldInfo field)
        {
            if(field == null)
                throw new ArgumentNullException("field");
            new GraphNodeEmitField(this, errorAt, opcode, field).ChainLin();
        }

        public void Emit(Token errorAt, OpCode opcode, ScriptMyLocal myLocal)
        {
            new GraphNodeEmitLocal(this, errorAt, opcode, myLocal).ChainLin();
        }

        public void Emit(Token errorAt, OpCode opcode, Type type)
        {
            new GraphNodeEmitType(this, errorAt, opcode, type).ChainLin();
        }

        public void Emit(Token errorAt, OpCode opcode, ScriptMyLabel myLabel)
        {
            if(opcode == OpCodes.Leave)
            {
                new GraphNodeEmitLabelLeave(this, errorAt, myLabel).ChainLin();
            }
            else
            {
                new GraphNodeEmitLabel(this, errorAt, opcode, myLabel).ChainLin();
            }
        }

        public void Emit(Token errorAt, OpCode opcode, ScriptMyLabel[] myLabels)
        {
            new GraphNodeEmitLabels(this, errorAt, opcode, myLabels).ChainLin();
        }

        public void Emit(Token errorAt, OpCode opcode, ScriptObjWriter method)
        {
            if(method == null)
                throw new ArgumentNullException("method");
            new GraphNodeEmitIntMeth(this, errorAt, opcode, method).ChainLin();
        }

        public void Emit(Token errorAt, OpCode opcode, MethodInfo method)
        {
            if(method == null)
                throw new ArgumentNullException("method");
            new GraphNodeEmitExtMeth(this, errorAt, opcode, method).ChainLin();
        }

        public void Emit(Token errorAt, OpCode opcode, ConstructorInfo ctor)
        {
            if(ctor == null)
                throw new ArgumentNullException("ctor");
            new GraphNodeEmitCtor(this, errorAt, opcode, ctor).ChainLin();
        }

        public void Emit(Token errorAt, OpCode opcode, double value)
        {
            new GraphNodeEmitDouble(this, errorAt, opcode, value).ChainLin();
        }

        public void Emit(Token errorAt, OpCode opcode, float value)
        {
            new GraphNodeEmitFloat(this, errorAt, opcode, value).ChainLin();
        }

        public void Emit(Token errorAt, OpCode opcode, int value)
        {
            new GraphNodeEmitInt(this, errorAt, opcode, value).ChainLin();
        }

        public void Emit(Token errorAt, OpCode opcode, string value)
        {
            new GraphNodeEmitString(this, errorAt, opcode, value).ChainLin();
        }

        public void MarkLabel(ScriptMyLabel myLabel)
        {
            myLabel.whereAmI = new GraphNodeMarkLabel(this, myLabel);
            myLabel.whereAmI.ChainLin();
        }

        /**
         * @brief Write the whole graph out to the object file.
         */
        public ScriptMyILGen WriteOutAll()
        {
            foreach(ScriptMyLocal loc in declaredLocals)
            {
                if(loc.isReferenced)
                    wrapped.DeclareLocal(loc);
            }
            foreach(ScriptMyLabel lbl in definedLabels)
            {
                wrapped.DefineLabel(lbl);
            }
            for(GraphNode gn = firstLin; gn != null; gn = gn.nextLin)
            {
                gn.WriteOutOne(wrapped);
            }
            return wrapped;
        }

        /**
         * @brief Perform optimizations.
         */
        public void Optimize()
        {
            if(curExcBlock != null)
                throw new Exception("exception block still open");

            // If an instruction says it doesn't fall through, remove all instructions to
            // the end of the block.
            for(GraphNode gn = firstLin; gn != null; gn = gn.nextLin)
            {
                if(!gn.CanFallThrough())
                {
                    GraphNode nn;
                    while(((nn = gn.nextLin) != null) && !(nn is GraphNodeBlock) &&
                                              !(nn is GraphNodeEndExceptionBlock))
                    {
                        if((gn.nextLin = nn.nextLin) != null)
                        {
                            nn.nextLin.prevLin = gn;
                        }
                    }
                }
            }

            // Scan for OpCodes.Leave instructions.
            // For each found, its target for flow analysis purposes is the beginning of the corresponding
            // finally block.  And the end of the finally block gets a conditional branch target of the 
            // leave instruction's target.  A leave instruction can unwind zero or more finally blocks.
            for(GraphNode gn = firstLin; gn != null; gn = gn.nextLin)
            {
                if(gn is GraphNodeEmitLabelLeave)
                {
                    GraphNodeEmitLabelLeave leaveInstr = (GraphNodeEmitLabelLeave)gn;         // the leave instruction
                    GraphNodeMarkLabel leaveTarget = leaveInstr.myLabel.whereAmI;             // label being targeted by leave
                    GraphNodeBeginExceptionBlock leaveTargetsTryBlock =                       // try block directly enclosing leave target
                        (leaveTarget == null) ? null : leaveTarget.tryBlock;              // ...it must not be unwound

                    // Step through try { }s from the leave instruction towards its target looking for try { }s with finally { }s.
                    // The leave instruction unconditionally branches to the beginning of the innermost one found.
                    // The end of the last one found conditionally branches to the leave instruction's target.
                    // If none found, the leave is a simple unconditional branch to its target.
                    GraphNodeBeginFinallyBlock innerFinallyBlock = null;
                    for(GraphNodeBeginExceptionBlock tryBlock = leaveInstr.tryBlock;
                         tryBlock != leaveTargetsTryBlock;
                         tryBlock = tryBlock.tryBlock)
                    {
                        if(tryBlock == null)
                            throw new Exception("leave target not at or outer to leave instruction");
                        GraphNodeCatchFinallyBlock cfb = tryBlock.catchFinallyBlock;
                        if(cfb is GraphNodeBeginFinallyBlock)
                        {
                            if(innerFinallyBlock == null)
                            {
                                leaveInstr.unwindTo = cfb;
                            }
                            innerFinallyBlock = (GraphNodeBeginFinallyBlock)cfb;
                        }
                    }

                    // The end of the outermost finally being unwound can conditionally jump to the target of the leave instruction.
                    // In the case of no finallies being unwound, the leave is just a simple unconditional branch.
                    if(innerFinallyBlock == null)
                    {
                        leaveInstr.unwindTo = leaveTarget;
                    }
                    else if(!innerFinallyBlock.leaveTargets.Contains(leaveTarget))
                    {
                        innerFinallyBlock.leaveTargets.Add(leaveTarget);
                    }
                }
            }

            // See which variables a particular block reads before writing.
            // This just considers the block itself and nothing that it branches to or fallsthru to.
            GraphNodeBlock currentBlock = null;
            for(GraphNode gn = firstLin; gn != null; gn = gn.nextLin)
            {
                if(gn is GraphNodeBlock)
                    currentBlock = (GraphNodeBlock)gn;
                ScriptMyLocal rdlcl = gn.ReadsLocal();
                if((rdlcl != null) &&
                    !currentBlock.localsWrittenBeforeRead.Contains(rdlcl) &&
                    !currentBlock.localsReadBeforeWritten.Contains(rdlcl))
                {
                    currentBlock.localsReadBeforeWritten.Add(rdlcl);
                }
                ScriptMyLocal wrlcl = gn.WritesLocal();
                if((wrlcl != null) &&
                    !currentBlock.localsWrittenBeforeRead.Contains(wrlcl) &&
                    !currentBlock.localsReadBeforeWritten.Contains(wrlcl))
                {
                    currentBlock.localsWrittenBeforeRead.Add(wrlcl);
                }
            }

            // For every block we branch to, add that blocks readables to our list of readables,
            // because we need to have those values valid on entry to our block.  But if we write the 
            // variable before we can possibly branch to that block, then we don't need to have it valid 
            // on entry to our block.  So basically it looks like the branch instruction is reading 
            // everything required by any blocks it can branch to.
            do
            {
                this.resolvedSomething = false;
                this.resolveSequence++;
                this.ResolveBlock((GraphNodeBlock)firstLin);
            } while(this.resolvedSomething);

            // Repeat the cutting loops as long as we keep finding stuff.
            bool didSomething;
            do
            {
                didSomething = false;

                // Strip out ldc.i4.1/xor/ldc.i4.1/xor
                for(GraphNode gn = firstLin; gn != null; gn = gn.nextLin)
                {
                    if(!(gn is GraphNodeEmit))
                        continue;
                    GraphNodeEmit xor2 = (GraphNodeEmit)gn;
                    if(xor2.opcode != OpCodes.Xor)
                        continue;
                    if(!(xor2.prevLin is GraphNodeEmit))
                        continue;
                    GraphNodeEmit ld12 = (GraphNodeEmit)xor2.prevLin;
                    if(ld12.opcode != OpCodes.Ldc_I4_1)
                        continue;
                    if(!(ld12.prevLin is GraphNodeEmit))
                        continue;
                    GraphNodeEmit xor1 = (GraphNodeEmit)ld12.prevLin;
                    if(xor1.opcode != OpCodes.Xor)
                        continue;
                    if(!(xor2.prevLin is GraphNodeEmit))
                        continue;
                    GraphNodeEmit ld11 = (GraphNodeEmit)xor1.prevLin;
                    if(ld11.opcode != OpCodes.Ldc_I4_1)
                        continue;
                    ld11.prevLin.nextLin = xor2.nextLin;
                    xor2.nextLin.prevLin = ld11.prevLin;
                    didSomething = true;
                }

                // Replace c{cond}/ldc.i4.1/xor/br{false,true} -> c{cond}/br{true,false}
                for(GraphNode gn = firstLin; gn != null; gn = gn.nextLin)
                {
                    if(!(gn is GraphNodeEmit))
                        continue;
                    GraphNodeEmit brft = (GraphNodeEmit)gn;
                    if((brft.opcode != OpCodes.Brfalse) && (brft.opcode != OpCodes.Brtrue))
                        continue;
                    if(!(brft.prevLin is GraphNodeEmit))
                        continue;
                    GraphNodeEmit xor = (GraphNodeEmit)brft.prevLin;
                    if(xor.opcode != OpCodes.Xor)
                        continue;
                    if(!(xor.prevLin is GraphNodeEmit))
                        continue;
                    GraphNodeEmit ldc = (GraphNodeEmit)xor.prevLin;
                    if(ldc.opcode != OpCodes.Ldc_I4_1)
                        continue;
                    if(!(ldc.prevLin is GraphNodeEmit))
                        continue;
                    GraphNodeEmit cmp = (GraphNodeEmit)ldc.prevLin;
                    if(cmp.opcode.StackBehaviourPop != StackBehaviour.Pop1_pop1)
                        continue;
                    if(cmp.opcode.StackBehaviourPush != StackBehaviour.Pushi)
                        continue;
                    cmp.nextLin = brft;
                    brft.prevLin = cmp;
                    brft.opcode = (brft.opcode == OpCodes.Brfalse) ? OpCodes.Brtrue : OpCodes.Brfalse;
                    didSomething = true;
                }

                // Replace c{cond}/br{false,true} -> b{!,}{cond}
                for(GraphNode gn = firstLin; gn != null; gn = gn.nextLin)
                {
                    if(!(gn is GraphNodeEmit))
                        continue;
                    GraphNodeEmit brft = (GraphNodeEmit)gn;
                    if((brft.opcode != OpCodes.Brfalse) && (brft.opcode != OpCodes.Brtrue))
                        continue;
                    if(!(brft.prevLin is GraphNodeEmit))
                        continue;
                    GraphNodeEmit cmp = (GraphNodeEmit)brft.prevLin;
                    if(cmp.opcode.StackBehaviourPop != StackBehaviour.Pop1_pop1)
                        continue;
                    if(cmp.opcode.StackBehaviourPush != StackBehaviour.Pushi)
                        continue;
                    cmp.prevLin.nextLin = brft;
                    brft.prevLin = cmp.prevLin;
                    bool brtru = (brft.opcode == OpCodes.Brtrue);
                    if(cmp.opcode == OpCodes.Ceq)
                        brft.opcode = brtru ? OpCodes.Beq : OpCodes.Bne_Un;
                    else if(cmp.opcode == OpCodes.Cgt)
                        brft.opcode = brtru ? OpCodes.Bgt : OpCodes.Ble;
                    else if(cmp.opcode == OpCodes.Cgt_Un)
                        brft.opcode = brtru ? OpCodes.Bgt_Un : OpCodes.Ble_Un;
                    else if(cmp.opcode == OpCodes.Clt)
                        brft.opcode = brtru ? OpCodes.Blt : OpCodes.Bge;
                    else if(cmp.opcode == OpCodes.Clt_Un)
                        brft.opcode = brtru ? OpCodes.Blt_Un : OpCodes.Bge_Un;
                    else
                        throw new Exception();
                    didSomething = true;
                }

                // Replace ld{c.i4.0,null}/br{ne.un,eq} -> br{true,false}
                for(GraphNode gn = firstLin; gn != null; gn = gn.nextLin)
                {
                    if(!(gn is GraphNodeEmit))
                        continue;
                    GraphNodeEmit brcc = (GraphNodeEmit)gn;
                    if((brcc.opcode != OpCodes.Bne_Un) && (brcc.opcode != OpCodes.Beq))
                        continue;
                    if(!(brcc.prevLin is GraphNodeEmit))
                        continue;
                    GraphNodeEmit ldc0 = (GraphNodeEmit)brcc.prevLin;
                    if((ldc0.opcode != OpCodes.Ldc_I4_0) && (ldc0.opcode != OpCodes.Ldnull))
                        continue;
                    ldc0.prevLin.nextLin = brcc;
                    brcc.prevLin = ldc0.prevLin;
                    brcc.opcode = (brcc.opcode == OpCodes.Bne_Un) ? OpCodes.Brtrue : OpCodes.Brfalse;
                    didSomething = true;
                }

                // Replace:
                //    ldloc v1
                //    stloc v2
                //    ld<anything> except ld<anything> v2
                //    ldloc v2
                //      ...v2 unreferenced hereafter
                // With:
                //    ld<anything> except ld<anything> v2
                //    ldloc v1
                for(GraphNode gn = firstLin; gn != null; gn = gn.nextLin)
                {

                    // check for 'ldloc v1' instruction
                    if(!(gn is GraphNodeEmitLocal))
                        continue;
                    GraphNodeEmitLocal ldlv1 = (GraphNodeEmitLocal)gn;
                    if(ldlv1.opcode != OpCodes.Ldloc)
                        continue;

                    // check for 'stloc v2' instruction
                    if(!(ldlv1.nextLin is GraphNodeEmitLocal))
                        continue;
                    GraphNodeEmitLocal stlv2 = (GraphNodeEmitLocal)ldlv1.nextLin;
                    if(stlv2.opcode != OpCodes.Stloc)
                        continue;

                    // check for 'ld<anything> except ld<anything> v2' instruction
                    if(!(stlv2.nextLin is GraphNodeEmit))
                        continue;
                    GraphNodeEmit ldany = (GraphNodeEmit)stlv2.nextLin;
                    if(!ldany.opcode.ToString().StartsWith("ld"))
                        continue;
                    if((ldany is GraphNodeEmitLocal) &&
                        ((GraphNodeEmitLocal)ldany).myLocal == stlv2.myLocal)
                        continue;

                    // check for 'ldloc v2' instruction
                    if(!(ldany.nextLin is GraphNodeEmitLocal))
                        continue;
                    GraphNodeEmitLocal ldlv2 = (GraphNodeEmitLocal)ldany.nextLin;
                    if(ldlv2.opcode != OpCodes.Ldloc)
                        continue;
                    if(ldlv2.myLocal != stlv2.myLocal)
                        continue;

                    // check that v2 is not needed after this at all
                    if(IsLocalNeededAfterThis(ldlv2, ldlv2.myLocal))
                        continue;

                    // make 'ld<anything>...' the first instruction
                    ldany.prevLin = ldlv1.prevLin;
                    ldany.prevLin.nextLin = ldany;

                    // make 'ldloc v1' the second instruction
                    ldany.nextLin = ldlv1;
                    ldlv1.prevLin = ldany;

                    // and make 'ldloc v1' the last instruction
                    ldlv1.nextLin = ldlv2.nextLin;
                    ldlv1.nextLin.prevLin = ldlv1;

                    didSomething = true;
                }

                // Remove all the stloc/ldloc that are back-to-back without the local
                // being needed afterwards.  If it is needed afterwards, replace the 
                // stloc/ldloc with dup/stloc.
                for(GraphNode gn = firstLin; gn != null; gn = gn.nextLin)
                {
                    if((gn is GraphNodeEmitLocal) &&
                        (gn.prevLin is GraphNodeEmitLocal))
                    {
                        GraphNodeEmitLocal stloc = (GraphNodeEmitLocal)gn.prevLin;
                        GraphNodeEmitLocal ldloc = (GraphNodeEmitLocal)gn;
                        if((stloc.opcode == OpCodes.Stloc) &&
                            (ldloc.opcode == OpCodes.Ldloc) &&
                            (stloc.myLocal == ldloc.myLocal))
                        {
                            if(IsLocalNeededAfterThis(ldloc, ldloc.myLocal))
                            {
                                GraphNodeEmitNull dup = new GraphNodeEmitNull(this, stloc.errorAt, OpCodes.Dup);
                                dup.nextLin = stloc;
                                dup.prevLin = stloc.prevLin;
                                stloc.nextLin = ldloc.nextLin;
                                stloc.prevLin = dup;
                                dup.prevLin.nextLin = dup;
                                stloc.nextLin.prevLin = stloc;
                                gn = stloc;
                            }
                            else
                            {
                                stloc.prevLin.nextLin = ldloc.nextLin;
                                ldloc.nextLin.prevLin = stloc.prevLin;
                                gn = stloc.prevLin;
                            }
                            didSomething = true;
                        }
                    }
                }

                // Remove all write-only local variables, ie, those with no ldloc[a] references.
                // Replace any stloc instructions with pops.
                for(GraphNode gn = firstLin; gn != null; gn = gn.nextLin)
                {
                    ScriptMyLocal rdlcl = gn.ReadsLocal();
                    if(rdlcl != null)
                        rdlcl.isReferenced = true;
                }
                for(GraphNode gn = firstLin; gn != null; gn = gn.nextLin)
                {
                    ScriptMyLocal wrlcl = gn.WritesLocal();
                    if((wrlcl != null) && !wrlcl.isReferenced)
                    {
                        if(!(gn is GraphNodeEmitLocal) || (((GraphNodeEmitLocal)gn).opcode != OpCodes.Stloc))
                        {
                            throw new Exception("expecting stloc");
                        }
                        GraphNodeEmitNull pop = new GraphNodeEmitNull(this, ((GraphNodeEmit)gn).errorAt, OpCodes.Pop);
                        pop.nextLin = gn.nextLin;
                        pop.prevLin = gn.prevLin;
                        gn.nextLin.prevLin = pop;
                        gn.prevLin.nextLin = pop;
                        gn = pop;
                        didSomething = true;
                    }
                }

                // Remove any Ld<const>/Dup,Pop.
                for(GraphNode gn = firstLin; gn != null; gn = gn.nextLin)
                {
                    if((gn is GraphNodeEmit) &&
                        (gn.nextLin is GraphNodeEmit))
                    {
                        GraphNodeEmit gne = (GraphNodeEmit)gn;
                        GraphNodeEmit nne = (GraphNodeEmit)gn.nextLin;
                        if(gne.isPoppable && (nne.opcode == OpCodes.Pop))
                        {
                            gne.prevLin.nextLin = nne.nextLin;
                            nne.nextLin.prevLin = gne.prevLin;
                            gn = gne.prevLin;
                            didSomething = true;
                        }
                    }
                }
            } while(didSomething);

            // Dump out the results.
            if(DEBUG)
            {
                Console.WriteLine("");
                Console.WriteLine(methName);
                Console.WriteLine("  resolveSequence=" + this.resolveSequence);

                Console.WriteLine("  Locals:");
                foreach(ScriptMyLocal loc in declaredLocals)
                {
                    Console.WriteLine("    " + loc.type.Name + "  " + loc.name);
                }

                Console.WriteLine("  Labels:");
                foreach(ScriptMyLabel lbl in definedLabels)
                {
                    Console.WriteLine("    " + lbl.name);
                }

                Console.WriteLine("  Code:");
                DumpCode();
            }
        }

        private void DumpCode()
        {
            int linSeqNos = 0;
            for(GraphNode gn = firstLin; gn != null; gn = gn.nextLin)
            {
                gn.linSeqNo = ++linSeqNos;
            }
            for(GraphNode gn = firstLin; gn != null; gn = gn.nextLin)
            {
                StringBuilder sb = new StringBuilder();
                gn.DebStringExt(sb);
                Console.WriteLine(sb.ToString());
                if(gn is GraphNodeBlock)
                {
                    GraphNodeBlock gnb = (GraphNodeBlock)gn;
                    foreach(ScriptMyLocal lcl in gnb.localsReadBeforeWritten)
                    {
                        Console.WriteLine("         reads " + lcl.name);
                    }
                }
            }
        }

        /**
         * @brief Scan the given block for branches to other blocks.
         *        For any locals read by those blocks, mark them as being read by this block, 
         *        provided this block has not written them by that point.  This makes it look 
         *        as though the branch instruction is reading all the locals needed by any 
         *        target blocks.
         */
        private void ResolveBlock(GraphNodeBlock currentBlock)
        {
            if(currentBlock.hasBeenResolved == this.resolveSequence)
                return;

            // So we don't recurse forever on a backward branch.
            currentBlock.hasBeenResolved = this.resolveSequence;

            // Assume we haven't written any locals yet.
            List<ScriptMyLocal> localsWrittenSoFar = new List<ScriptMyLocal>();

            // Scan through the instructions in this block.
            for(GraphNode gn = currentBlock; gn != null;)
            {

                // See if the instruction writes a local we don't know about yet.
                ScriptMyLocal wrlcl = gn.WritesLocal();
                if((wrlcl != null) && !localsWrittenSoFar.Contains(wrlcl))
                {
                    localsWrittenSoFar.Add(wrlcl);
                }

                // Scan through all the possible next instructions after this.
                // Note that if we are in the first part of a try/catch/finally block, 
                // every instruction conditionally branches to the beginning of the 
                // second part (the catch/finally block).
                GraphNode nextFallthruNode = null;
                foreach(GraphNode nn in gn.NextNodes)
                {
                    if(nn is GraphNodeBlock)
                    {
                        // Start of a block, go through all locals needed by that block on entry.
                        GraphNodeBlock nextBlock = (GraphNodeBlock)nn;
                        ResolveBlock(nextBlock);
                        foreach(ScriptMyLocal readByNextBlock in nextBlock.localsReadBeforeWritten)
                        {
                            // If this block hasn't written it by now and this block doesn't already
                            // require it on entry, say this block requires it on entry.
                            if(!localsWrittenSoFar.Contains(readByNextBlock) &&
                                !currentBlock.localsReadBeforeWritten.Contains(readByNextBlock))
                            {
                                currentBlock.localsReadBeforeWritten.Add(readByNextBlock);
                                this.resolvedSomething = true;
                            }
                        }
                    }
                    else
                    {
                        // Not start of a block, should be normal fallthru instruction.
                        if(nextFallthruNode != null)
                            throw new Exception("more than one fallthru from " + gn.ToString());
                        nextFallthruNode = nn;
                    }
                }

                // Process next instruction if it isn't the start of a block.
                if(nextFallthruNode == gn)
                    throw new Exception("can't fallthru to self");
                gn = nextFallthruNode;
            }
        }

        /**
         * @brief Figure out whether the value in a local var is needed after the given instruction.
         *        True if we reach the end of the program on all branches before reading it
         *        True if we write the local var on all branches before reading it
         *        False otherwise
         */
        private bool IsLocalNeededAfterThis(GraphNode node, ScriptMyLocal local)
        {
            do
            {
                GraphNode nextFallthruNode = null;
                foreach(GraphNode nn in node.NextNodes)
                {
                    if(nn is GraphNodeBlock)
                    {
                        if(((GraphNodeBlock)nn).localsReadBeforeWritten.Contains(local))
                        {
                            return true;
                        }
                    }
                    else
                    {
                        nextFallthruNode = nn;
                    }
                }
                node = nextFallthruNode;
                if(node == null)
                    return false;
                if(node.ReadsLocal() == local)
                    return true;
            } while(node.WritesLocal() != local);
            return false;
        }

        public static void PadToLength(StringBuilder sb, int len, string str)
        {
            int pad = len - sb.Length;
            if(pad < 0)
                pad = 0;
            sb.Append(str.PadLeft(pad));
        }
    }
}