/* * 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.XMREngine { /** * @brief This is a list that keeps track of types pushed on the evaluation stack. */ public class StackDepth : List { public List isBoxeds = new List (); /** * @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 nextNodes, nextNodesCatchFinally; public System.Collections.Generic.IEnumerable 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 { private GraphNode gn; public NNEnumerableCatchFinally (GraphNode gn) { this.gn = gn; } System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.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 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 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 localsWrittenBeforeRead = new List (); public List localsReadBeforeWritten = new List (); 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 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 leaveTargets = new List (); 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 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 GetNNEnumerable () { return new NNEnumerable (this, typeof (NNEnumerator)); } private class NNEnumerator : NNEnumeratorBase { private GraphNodeEmitNullEndfinally gn; private IEnumerator 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 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 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 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 { 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 System.Collections.Generic.IEnumerable.GetEnumerator () { return (System.Collections.Generic.IEnumerator) 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 { protected GraphNode nn; public abstract bool MoveNext (); public abstract void Reset (); GraphNode System.Collections.Generic.IEnumerator.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 declaredLocals = new List (); private List definedLabels = new List (); 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 except ld v2 * ldloc v2 * ...v2 unreferenced hereafter * With: * ld except ld 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 except ld 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...' 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/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 localsWrittenSoFar = new List (); /* * 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)); } } }