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

2638 lines
102 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.XMREngine
{
/**
* @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));
}
}
}