You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

291 lines
12 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
namespace gaemstone.Utility.IL;
public class ILGeneratorWrapper
{
private readonly DynamicMethod _method;
private readonly ILGenerator _il;
private readonly List<ILocal> _locals = new();
private readonly List<(int Offset, int Indent, OpCode Code, object? Arg)> _instructions = new();
private readonly Dictionary<Label, int> _labelToOffset = new();
private readonly Stack<BlockImpl> _indents = new();
public ILGeneratorWrapper(DynamicMethod method)
{
_method = method;
_il = method.GetILGenerator();
}
public string ToReadableString()
{
var sb = new StringBuilder();
sb.AppendLine("Parameters:");
foreach (var (param, index) in _method.GetParameters().Select((p, i) => (p, i)))
sb.AppendLine($" Argument({index}, {param.ParameterType.Name})");
sb.AppendLine("Return:");
sb.AppendLine($" {_method.ReturnType.Name}");
sb.AppendLine();
sb.AppendLine("Locals:");
foreach (var local in _locals)
sb.AppendLine($" {local}");
sb.AppendLine();
sb.AppendLine("Instructions:");
foreach (var (offset, indent, code, arg) in _instructions) {
sb.Append(" ");
// Append instruction offset.
if (offset < 0) sb.Append(" ");
else sb.Append($"0x{offset:X4} ");
// Append instruction opcode.
if (code == OpCodes.Nop) sb.Append(" ");
else sb.Append($"{code.Name,-12}");
// Append indents.
for (var i = 0; i < indent; i++)
sb.Append("| ");
// Append instruction argument.
if (code == OpCodes.Nop) sb.Append("// ");
switch (arg) {
case Label label: sb.Append($"Label(0x{_labelToOffset.GetValueOrDefault(label, -1):X4})"); break;
case not null: sb.Append(arg); break;
}
sb.AppendLine();
}
return sb.ToString();
}
public IArgument Argument(int index)
{
var type = _method.GetParameters()[index].ParameterType;
if (type.IsByRefLike) return new ArgumentImpl(index, type);
return (IArgument)Activator.CreateInstance(typeof(ArgumentImpl<>).MakeGenericType(type), index)!;
}
public IArgument<T> Argument<T>(int index) => (IArgument<T>)Argument(index);
public ILocal Local(Type type, string? name = null)
{
var builder = _il.DeclareLocal(type);
var local = type.IsByRefLike ? new LocalImpl(builder, name)
: (ILocal)Activator.CreateInstance(typeof(LocalImpl<>).MakeGenericType(type), builder, name)!;
_locals.Add(local);
return local;
}
public ILocal<T> Local<T>(string? name = null) => (ILocal<T>)Local(typeof(T), name);
public ILocal<Array> LocalArray(Type type, string? name = null) => (ILocal<Array>)Local(type.MakeArrayType(), name);
public ILocal<T[]> LocalArray<T>(string? name = null) => (ILocal<T[]>)Local(typeof(T).MakeArrayType(), name);
public Label DefineLabel() => _il.DefineLabel();
public void MarkLabel(Label label)
{
_instructions.Add((-1, _indents.Count, OpCodes.Nop, label));
_labelToOffset.Add(label, _il.ILOffset);
_il.MarkLabel(label);
}
private void AddInstr(OpCode code, object? arg = null) => _instructions.Add((_il.ILOffset, _indents.Count, code, arg));
public void Comment(string comment) => _instructions.Add((-1, _indents.Count, OpCodes.Nop, comment));
internal void Emit(OpCode code) { AddInstr(code, null); _il.Emit(code); }
internal void Emit(OpCode code, int arg) { AddInstr(code, arg); _il.Emit(code, arg); }
internal void Emit(OpCode code, Type type) { AddInstr(code, type); _il.Emit(code, type); }
internal void Emit(OpCode code, Label label) { AddInstr(code, label); _il.Emit(code, label); }
internal void Emit(OpCode code, ILocal local) { AddInstr(code, local); _il.Emit(code, local.Builder); }
internal void Emit(OpCode code, IArgument arg) { AddInstr(code, arg); _il.Emit(code, arg.Index); }
internal void Emit(OpCode code, MethodInfo method) { AddInstr(code, method); _il.Emit(code, method); }
internal void Emit(OpCode code, ConstructorInfo constr) { AddInstr(code, constr); _il.Emit(code, constr); }
public void LoadNull() => Emit(OpCodes.Ldnull);
public void LoadConst(int value) => Emit(OpCodes.Ldc_I4, value);
public void Load(IArgument arg) => Emit(OpCodes.Ldarg, arg);
public void LoadAddr(IArgument arg) => Emit(OpCodes.Ldarga, arg);
public void Load(ILocal local) => Emit(OpCodes.Ldloc, local);
public void LoadAddr(ILocal local) => Emit(OpCodes.Ldloca, local);
public void Store(ILocal local) => Emit(OpCodes.Stloc, local);
public void Set(ILocal<int> local, int value) { LoadConst(value); Store(local); }
public void LoadLength() { Emit(OpCodes.Ldlen); Emit(OpCodes.Conv_I4); }
public void LoadLength(IArgument<Array> array) { Load(array); LoadLength(); }
public void LoadLength(ILocal<Array> array) { Load(array); LoadLength(); }
public void LoadObj(Type type) => Emit(OpCodes.Ldobj, type);
public void LoadObj(ILocal local) { LoadAddr(local); LoadObj(local.LocalType); }
public void LoadObj(IArgument arg) { LoadAddr(arg); LoadObj(arg.ArgumentType); }
public void LoadElem(Type type) => Emit(OpCodes.Ldelem, type);
public void LoadElem(Type type, int index) { LoadConst(index); LoadElem(type); }
public void LoadElem(Type type, ILocal<int> index) { Load(index); LoadElem(type); }
public void LoadElem(Type type, IArgument<Array> array, int index) { Load(array); LoadElem(type, index); }
public void LoadElem(Type type, IArgument<Array> array, ILocal<int> index) { Load(array); LoadElem(type, index); }
public void LoadElem(Type type, ILocal<Array> array, int index) { Load(array); LoadElem(type, index); }
public void LoadElem(Type type, ILocal<Array> array, ILocal<int> index) { Load(array); LoadElem(type, index); }
public void LoadElemRef() => Emit(OpCodes.Ldelem_Ref);
public void LoadElemRef(int index) { LoadConst(index); LoadElemRef(); }
public void LoadElemRef(ILocal<int> index) { Load(index); LoadElemRef(); }
public void LoadElemRef(IArgument<Array> array, int index) { Load(array); LoadElemRef(index); }
public void LoadElemRef(IArgument<Array> array, ILocal<int> index) { Load(array); LoadElemRef(index); }
public void LoadElemRef(ILocal<Array> array, int index) { Load(array); LoadElemRef(index); }
public void LoadElemRef(ILocal<Array> array, ILocal<int> index) { Load(array); LoadElemRef(index); }
public void LoadElemEither(Type type) { if (type.IsValueType) LoadElem(type); else LoadElemRef(); }
public void LoadElemEither(Type type, int index) { if (type.IsValueType) LoadElem(type, index); else LoadElemRef(index); }
public void LoadElemEither(Type type, ILocal<int> index) { if (type.IsValueType) LoadElem(type, index); else LoadElemRef(index); }
public void LoadElemEither(Type type, IArgument<Array> array, int index) { if (type.IsValueType) LoadElem(type, array, index); else LoadElemRef(array, index); }
public void LoadElemEither(Type type, IArgument<Array> array, ILocal<int> index) { if (type.IsValueType) LoadElem(type, array, index); else LoadElemRef(array, index); }
public void LoadElemEither(Type type, ILocal<Array> array, int index) { if (type.IsValueType) LoadElem(type, array, index); else LoadElemRef(array, index); }
public void LoadElemEither(Type type, ILocal<Array> array, ILocal<int> index) { if (type.IsValueType) LoadElem(type, array, index); else LoadElemRef(array, index); }
public void LoadElemAddr(Type type) => Emit(OpCodes.Ldelema, type);
public void LoadElemAddr(Type type, int index) { LoadConst(index); LoadElemAddr(type); }
public void LoadElemAddr(Type type, ILocal<int> index) { Load(index); LoadElemAddr(type); }
public void LoadElemAddr(Type type, IArgument<Array> array, int index) { Load(array); LoadElemAddr(type, index); }
public void LoadElemAddr(Type type, IArgument<Array> array, ILocal<int> index) { Load(array); LoadElemAddr(type, index); }
public void LoadElemAddr(Type type, ILocal<Array> array, int index) { Load(array); LoadElemAddr(type, index); }
public void LoadElemAddr(Type type, ILocal<Array> array, ILocal<int> index) { Load(array); LoadElemAddr(type, index); }
public void Load(PropertyInfo info) => CallVirt(info.GetMethod!);
public void Load(ILocal obj, PropertyInfo info) { Load(obj); Load(info); }
public void Load(IArgument obj, PropertyInfo info) { Load(obj); Load(info); }
public void Add() => Emit(OpCodes.Add);
public void Increment(ILocal<int> local) { Load(local); LoadConst(1); Add(); Store(local); }
public void Init(Type type) => Emit(OpCodes.Initobj, type);
public void Init<T>() where T : struct => Emit(OpCodes.Initobj, typeof(T));
public void New(ConstructorInfo constructor) => Emit(OpCodes.Newobj, constructor);
public void New(Type type) => New(type.GetConstructors().Single());
public void New(Type type, params Type[] paramTypes) => New(type.GetConstructor(paramTypes)!);
public void Cast(Type type) => Emit(OpCodes.Castclass, type);
public void Cast<T>() => Cast(typeof(T));
public void Goto(Label label) => Emit(OpCodes.Br, label);
public void GotoIfTrue(Label label) => Emit(OpCodes.Brtrue, label);
public void GotoIfFalse(Label label) => Emit(OpCodes.Brfalse, label);
public void GotoIf(Label label, ILocal<int> a, Comparison op, ILocal<int> b)
{ Load(a); Load(b); Emit(op.Code, label); }
public void GotoIfNull(Label label, ILocal<object> local)
{ Load(local); Emit(OpCodes.Brfalse, label); }
public void GotoIfNotNull(Label label, ILocal<object> local)
{ Load(local); Emit(OpCodes.Brtrue, label); }
public void Call(MethodInfo method) => Emit(OpCodes.Call, method);
public void CallVirt(MethodInfo method) => Emit(OpCodes.Callvirt, method);
public void Return() => Emit(OpCodes.Ret);
public IDisposable For(Action loadMax, out ILocal<int> current)
{
var r = Random.Shared.Next(10000, 100000);
Comment($"INIT for loop {r}");
var curLocal = current = Local<int>($"index_{r}");
var maxLocal = Local<int>($"length_{r}");
var bodyLabel = DefineLabel();
var testLabel = DefineLabel();
Set(curLocal, 0);
loadMax(); Store(maxLocal);
Comment($"BEGIN for loop {r}");
Goto(testLabel);
MarkLabel(bodyLabel);
var indent = Indent();
return Block(() => {
Increment(curLocal);
MarkLabel(testLabel);
GotoIf(bodyLabel, curLocal, Comparison.LessThan, maxLocal);
indent.Dispose();
Comment($"END for loop {r}");
});
}
public IDisposable Block(Action onClose)
=> new BlockImpl(onClose);
public IDisposable Indent()
{
BlockImpl indent = null!;
indent = new(() => { if (_indents.Pop() != indent) throw new InvalidOperationException(); });
_indents.Push(indent);
return indent;
}
internal class BlockImpl : IDisposable
{
public Action OnClose { get; }
public BlockImpl(Action onClose) => OnClose = onClose;
public void Dispose() => OnClose();
}
internal class ArgumentImpl : IArgument
{
public int Index { get; }
public Type ArgumentType { get; }
public ArgumentImpl(int index, Type type) { Index = index; ArgumentType = type; }
public override string ToString() => $"Argument({Index}, {ArgumentType.Name})";
}
internal class ArgumentImpl<T> : ArgumentImpl, IArgument<T>
{ public ArgumentImpl(int index) : base(index, typeof(T)) { } }
internal class LocalImpl : ILocal
{
public LocalBuilder Builder { get; }
public string? Name { get; }
public Type LocalType => Builder.LocalType;
public LocalImpl(LocalBuilder builder, string? name) { Builder = builder; Name = name; }
public override string ToString() => $"Local({Builder.LocalIndex}, {LocalType.Name}){(Name != null ? $" // {Name}" : "")}";
}
internal class LocalImpl<T> : LocalImpl, ILocal<T>
{ public LocalImpl(LocalBuilder builder, string? name) : base(builder, name) { } }
}
public class Comparison
{
public static Comparison NotEqual { get; } = new(OpCodes.Bne_Un);
public static Comparison LessThan { get; } = new(OpCodes.Blt);
public static Comparison LessOrEq { get; } = new(OpCodes.Ble);
public static Comparison Equal { get; } = new(OpCodes.Beq);
public static Comparison GreaterOrEq { get; } = new(OpCodes.Bge);
public static Comparison GreaterThan { get; } = new(OpCodes.Bgt);
public OpCode Code { get; }
private Comparison(OpCode code) => Code = code;
}
public interface IArgument
{
int Index { get; }
Type ArgumentType { get; }
}
public interface IArgument<out T>
: IArgument { }
public interface ILocal
{
LocalBuilder Builder { get; }
Type LocalType { get; }
}
public interface ILocal<out T>
: ILocal { }