2638 lines
102 KiB
C#
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));
|
|
}
|
|
}
|
|
}
|