OpenSimMirror/OpenSim/Region/ScriptEngine/YEngine/MMRScriptCollector.cs

3106 lines
109 KiB
C#

/*
* 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));
}
}
}