//#define DUMP
//#define CIL
//#define DEREFERENCE_VTABLE
#if DEREFERENCE_VTABLE && !CIL
#error 'DEREFERENCE_VTABLE' requires 'CIL'
#endif
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading.Tasks;
using System.Threading;
using System.Runtime.InteropServices;
using miew.Debugging;
using miew.Enumerable;
using miew.Tokenization;
using SysType = System.Type;
namespace agree
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary>
/// Planning a quick-check strategy in advance preserves a specified order for checking (the ends of) multiple
/// quick-check paths, yet without unneccesarily re-traversing any part of the TFS.
/// </summary>
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public sealed class QuickCheckStrategy
{
static readonly Char[] dot_space = { '.', ' ' };
static readonly Char[] outer_trim = { '\"', '\'', '(', ')', '[', ']' };
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// opcodes for the quick-check strategy steps
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public enum Op
{
Top = 1,
Feature = 2,
Check = 3,
ContinueFrom = 4,
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// a step in a quick-check strategy
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[DebuggerDisplay("{ToString(),nq}")]
public struct Entry
{
public Entry(Op op, int arg)
{
this.op = op;
this.arg = arg;
this.next_section = 0;
this.ix_slot = 0;
}
public Entry(Op op, int arg, int next_section, int ix_slot)
{
this.op = op;
this.arg = arg;
this.next_section = next_section;
this.ix_slot = ix_slot;
}
public Op op;
public int arg;
public int next_section;
public int ix_slot;
#if DEBUG
public override string ToString()
{
String s = String.Format("{0,6} -> {1:0000}", ix_slot == -1 ? "" : "[" + ix_slot.ToString("0000") + "]", next_section);
switch (op)
{
case Op.ContinueFrom:
s += String.Format("{0,13} {1:0000}", op, arg);
break;
case Op.Top:
case Op.Check:
s += String.Format("{0,13}", op);
break;
case Op.Feature:
s += String.Format("{0,13} {1,4} ({2})",
op,
arg,
Grammar._singleton.tm.feat_arr[arg].feature.ToUpper());
break;
}
return s;
}
#endif
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TypeMgr tm;
Entry[] entries;
int c_slots;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary>
/// Constructor. Analyzes the specified paths to develop the strategy.
/// </summary>
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public QuickCheckStrategy(TypeMgr tm, IEnumerable<String> paths)
{
this.tm = tm;
List<Entry> ent = new List<Entry>();
Dictionary<String, int> path_map = new Dictionary<String, int>();
Dictionary<int, int> slot_map = new Dictionary<int, int>();
IList<String> rap = tm.config.grammar.rule_args_path;
foreach (String _p in paths)
{
String[] path = _p.Trim(outer_trim).ToLower().Split(dot_space, StringSplitOptions.RemoveEmptyEntries);
if (path.Take(rap.Count).SequenceEqual(rap))
path = path.Skip(rap.Count).ToArray();
int ix_continue;
int j;
for (j = path.Length; j > 0; j--)
{
if (path_map.TryGetValue(path.Take(j).StringJoin("."), out ix_continue))
{
if (!slot_map.ContainsKey(ix_continue))
slot_map.Add(ix_continue, slot_map.Count);
ent.Add(new Entry(Op.ContinueFrom, ix_continue));
goto emit_start;
}
}
ent.Add(new Entry(Op.Top, 0));
ix_continue = -1;
emit_start:
for (; j < path.Length; j++)
{
if (ix_continue != -1)
ix_continue = -1;
else
{
String s_p = path.Take(j + 1).StringJoin(".");
//Console.WriteLine("{0,3} {1}", ent.Count, s_p);
path_map.Add(s_p, ent.Count);
}
ent.Add(new Entry(Op.Feature, tm.feat_map[path[j]].i_feat));
}
ent.Add(new Entry(Op.Check, 0));
}
c_slots = slot_map.Count;
entries = new Entry[ent.Count];
int next = ent.Count;
for (int i = ent.Count - 1; i >= 0; i--)
{
Entry e = ent[i];
int i_slot;
if (!slot_map.TryGetValue(i, out i_slot))
i_slot = -1;
int arg = e.arg;
if (e.op == Op.ContinueFrom)
arg = slot_map[arg];
entries[i] = new Entry(e.op, arg, next, i_slot);
if (e.op == Op.ContinueFrom || e.op == Op.Top)
next = i;
}
#if DUMP
int ix = 0;
foreach (Entry e in entries)
Console.WriteLine("{0:0000} {1}", ix++, e.ToString());
#endif
#if CIL
/// Generate a customized, unrolled CIL function for this quick-check strategy.
CanSkip = emit_CanSkip();
#endif
}
#if CIL
public delegate bool D(Tfs tfs1, Tfs tfs2);
readonly public D CanSkip;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary>
/// Emit unrolled code which executes the strategy on the given TFSes at runtime.
/// </summary>
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public D emit_CanSkip()
{
const BindingFlags bf = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
DynamicMethod dm = new DynamicMethod(
String.Empty,
typeof(bool),
new SysType[]
{
typeof(QuickCheckStrategy), // arg.0 'this'
typeof(Tfs), // arg.1 'tfs1'
typeof(Tfs) // arg.2 'tfs2'
},
typeof(QuickCheckStrategy),
false);
ILGenerator il = dm.GetILGenerator();
Dictionary<int, Label> section_labels = entries
.Select(ent => ent.next_section)
.Distinct()
.ToDictionary(eix => eix, eix => il.DefineLabel());
Retrolabel _ret_true = default(Retrolabel);
/// v===================== begin emiited code =====================v
il.DeclareLocal(typeof(Edge*)); // loc.0 'rge1'
il.DeclareLocal(typeof(Edge*)); // loc.1 'rge2'
il.DeclareLocal(typeof(Edge)); // loc.2 'e1'
il.DeclareLocal(typeof(Edge)); // loc.3 'e2'
#if DEREFERENCE_VTABLE
il.DeclareLocal(typeof(IntPtr)); // loc.4 'Tfs.TryGetEdge'
il.DeclareLocal(typeof(IntPtr)); // loc.5 'Tfs.TryGetEdge'
/// loc.4 = &tfs1.TryGetEdge;
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldvirtftn, typeof(Tfs).GetMethod("TryGetEdge"));
il.Emit(OpCodes.Stloc_S, (sbyte)4);
/// loc.5 = &tfs2.TryGetEdge;
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Ldvirtftn, typeof(Tfs).GetMethod("TryGetEdge"));
il.Emit(OpCodes.Stloc_S, (sbyte)5);
String call_type = "calli";
#else
String call_type = "callvirt";
#endif
/// Edge* rge1 = stackalloc Edge[c_slots];
il.EmitLdcI4(Marshal.SizeOf(typeof(Edge)) * c_slots);
il.Emit(OpCodes.Localloc);
il.Emit(OpCodes.Stloc_0);
/// Edge* rge2 = stackalloc Edge[c_slots];
il.EmitLdcI4(Marshal.SizeOf(typeof(Edge)) * c_slots);
il.Emit(OpCodes.Localloc);
il.Emit(OpCodes.Stloc_1);
Debug.Assert(entries[0].op == Op.Top);
int i_feat, i_slot, ix = 0;
do
{
Label _sect_label;
if (section_labels.TryGetValue(ix, out _sect_label))
il.MarkLabel(_sect_label);
switch (entries[ix].op)
{
case Op.Top:
/// e1 = tfs1.Edge;
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldfld, typeof(Tfs).GetField("Edge", bf));
il.Emit(OpCodes.Stloc_2);
/// e2 = tfs2.Edge;
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Ldfld, typeof(Tfs).GetField("Edge", bf));
il.Emit(OpCodes.Stloc_3);
break;
case Op.ContinueFrom:
i_slot = entries[ix].arg;
/// e1 = rge1[i_slot];
il.Emit(OpCodes.Ldloc_0);
il.EmitLdcI4(i_slot * Marshal.SizeOf(typeof(Edge)));
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ldobj, typeof(Edge));
il.Emit(OpCodes.Stloc_2);
/// e2 = rge2[i_slot];
il.Emit(OpCodes.Ldloc_1);
il.EmitLdcI4(i_slot * Marshal.SizeOf(typeof(Edge)));
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ldobj, typeof(Edge));
il.Emit(OpCodes.Stloc_3);
break;
case Op.Feature:
i_feat = entries[ix].arg;
/// if ((e1.FlagsId & Edge.Flag.EtmNonBareType) == 0 || ...
il.Emit(OpCodes.Ldloca_S, (sbyte)2);
il.Emit(OpCodes.Ldfld, typeof(Edge).GetField("FlagsId", bf));
il.EmitLdcI4((int)Edge.Flag.EtmNonBareType);
il.Emit(OpCodes.And);
il.Emit(OpCodes.Brfalse, section_labels[entries[ix].next_section]);
/// ... (e2.FlagsId & Edge.Flag.EtmNonBareType) == 0 ...
il.Emit(OpCodes.Ldloca_S, (sbyte)3);
il.Emit(OpCodes.Ldfld, typeof(Edge).GetField("FlagsId", bf));
il.EmitLdcI4((int)Edge.Flag.EtmNonBareType);
il.Emit(OpCodes.And);
il.Emit(OpCodes.Brfalse, section_labels[entries[ix].next_section]);
/// ... !tfs1.TryGetEdge(arg, e1.Mark, out e1) ...
il.Emit(OpCodes.Ldarg_1);
il.EmitLdcI4(i_feat);
il.Emit(OpCodes.Ldloca_S, (sbyte)2);
il.Emit(OpCodes.Ldfld, typeof(Edge).GetField("Mark", bf));
il.Emit(OpCodes.Ldloca_S, (sbyte)2);
#if DEREFERENCE_VTABLE
il.Emit(OpCodes.Ldloc_S, (sbyte)4);
il.EmitCalli(OpCodes.Calli,
CallingConventions.HasThis,
typeof(bool),
new SysType[] { typeof(int), typeof(int), typeof(Edge).MakeByRefType() },
null);
#else
il.Emit(OpCodes.Callvirt, typeof(Tfs).GetMethod("TryGetEdge"));
#endif
il.Emit(OpCodes.Brfalse, section_labels[entries[ix].next_section]);
/// ... !tfs2.TryGetEdge(arg, e2.Mark, out e2)) ...
il.Emit(OpCodes.Ldarg_2);
il.EmitLdcI4(i_feat);
il.Emit(OpCodes.Ldloca_S, (sbyte)3);
il.Emit(OpCodes.Ldfld, typeof(Edge).GetField("Mark", bf));
il.Emit(OpCodes.Ldloca_S, (sbyte)3);
#if DEREFERENCE_VTABLE
il.Emit(OpCodes.Ldloc_S, (sbyte)5);
il.EmitCalli(OpCodes.Calli,
CallingConventions.HasThis,
typeof(bool),
new SysType[] { typeof(int), typeof(int), typeof(Edge).MakeByRefType() },
null);
#else
il.Emit(OpCodes.Callvirt, typeof(Tfs).GetMethod("TryGetEdge"));
#endif
il.Emit(OpCodes.Brfalse, section_labels[entries[ix].next_section]);
i_slot = entries[ix].ix_slot;
if (i_slot != -1)
{
/// rge1[i_slot] = e1;
il.Emit(OpCodes.Ldloc_0);
il.EmitLdcI4(i_slot * Marshal.SizeOf(typeof(Edge)));
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ldloc_2);
il.Emit(OpCodes.Stobj, typeof(Edge));
/// rge2[i_slot] = e2;
il.Emit(OpCodes.Ldloc_1);
il.EmitLdcI4(i_slot * Marshal.SizeOf(typeof(Edge)));
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ldloc_3);
il.Emit(OpCodes.Stobj, typeof(Edge));
}
break;
case Op.Check:
/// if (!tm.CanUnify(e1.FlagsId, e2.FlagsId)) ...
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, typeof(QuickCheckStrategy).GetField("tm", bf));
il.Emit(OpCodes.Ldloca_S, (sbyte)2);
il.Emit(OpCodes.Ldfld, typeof(Edge).GetField("FlagsId", bf));
il.Emit(OpCodes.Ldloca_S, (sbyte)3);
il.Emit(OpCodes.Ldfld, typeof(Edge).GetField("FlagsId", bf));
il.Emit(OpCodes.Call, typeof(TypeMgr)
.GetMethod("CanUnify",
bf,
null,
new SysType[] { typeof(Edge.Flag), typeof(Edge.Flag) },
null));
/// emit 'true' return code the first time it's needed
if (_ret_true.Equals(default(Retrolabel)))
{
Label _skip = il.DefineLabel();
il.Emit(OpCodes.Brtrue_S, _skip);
/// return true;
_ret_true = new Retrolabel(il);
il.EmitLdcI4(1);
il.Emit(OpCodes.Ret);
il.MarkLabel(_skip);
}
else
il.Emit(OpCodes.Brfalse, _ret_true);
break;
}
ix++;
}
while (ix < entries.Length);
/// return false;
il.EmitLdcI4(0);
il.Emit(OpCodes.Ret);
/// ^===================== end emiited code =====================^
Console.WriteLine("Compiled quick-check strategy to CIL (using '{0}'): {1} bytes", call_type, il.ILOffset);
return (D)dm.CreateDelegate(typeof(D), this);
}
#else
struct EdgePair
{
public Edge e1;
public Edge e2;
};
struct EdgePair2
{
public Edge.Flag e1_f;
public int e1_m;
public Edge.Flag e2_f;
public int e2_m;
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary>
/// Execute the strategy on the given TFSes at runtime.
/// </summary>
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public unsafe bool CanSkip(Tfs tfs1, Tfs tfs2)
{
EdgePair* _rge = stackalloc EdgePair[c_slots + 1];
EdgePair* rge = _rge + 1;
EdgePair _ep_top;
_ep_top.e1 = tfs1.Edge;
_ep_top.e2 = tfs2.Edge;
if (tfs2 is TfsSection)
tfs2 = ((TfsSection)tfs2).mother;
Debug.Assert(entries[0].op == Op.Top);
EdgePair* ep = &_ep_top;
fixed (Entry* _pent = entries)
{
for (Entry* pent_cur = _pent + 1, pent_end = pent_cur + entries.Length; pent_cur < pent_end; )
{
switch (pent_cur->op)
{
case Op.Top:
ep = &_ep_top;
break;
case Op.ContinueFrom:
ep = rge + pent_cur->arg;
break;
case Op.Feature:
if ((ep->e1.FlagsId & ep->e2.FlagsId & Edge.Flag.EtmNonBareType) != 0)
{
int i_feat;
EdgePair2* ep_next = (EdgePair2*)(rge + pent_cur->ix_slot); // -1 is ok ix_slot here
if ((ep_next->e1_f = tfs1.TryGetFlagsMark((i_feat = pent_cur->arg), ep->e1.Mark, out ep_next->e1_m)) != 0 &&
(ep_next->e2_f = tfs2.TryGetFlagsMark(i_feat, ep->e2.Mark, out ep_next->e2_m)) != 0)
{
ep = (EdgePair*)ep_next;
break;
}
}
pent_cur = _pent + pent_cur->next_section;
continue; // note: not incrementing 'pent_cur'
case Op.Check:
Edge.Flag f1, f2;
if ((f1 = ep->e1.FlagsId) != 0 && f1 != (f2 = ep->e2.FlagsId) && f2 != 0 && !tm.CanUnify(f1, f2))
return true;
break;
}
pent_cur++;
}
}
return false;
}
#endif
};
}