Skip to content

Instantly share code, notes, and snippets.

@HurricanKai
Created September 26, 2018 12:43
Show Gist options
  • Save HurricanKai/e04895df3b1959e32b6fb3e3dffef2ed to your computer and use it in GitHub Desktop.
Save HurricanKai/e04895df3b1959e32b6fb3e3dffef2ed to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Mono.Cecil;
using Mono.Cecil.Cil;
using NLog;
namespace SM2.Experimental.CustomCLR
{
public class Scoreboards
{
public const string VALUE = "__value";
}
// big parts stolen from PearlCLR
public class CLR
{
private AssemblyDefinition assembly;
private Logger clrLogger;
private Dictionary<string, StructDefinition> FullSymbolToTypeRef { get; } = new Dictionary<string, StructDefinition>();
public EntityStack EntityStack { get; private set; }
Dictionary<Instruction, string> JumpTargets { get; set; }
public CLR(string path)
{
assembly = AssemblyDefinition.ReadAssembly(path);
clrLogger = LogManager.GetCurrentClassLogger();
}
public void Run()
{
// Dependencies Woud be worth a note
// but nah :d
// Types
ProcessExportedTypes();
// Trace from Main Function
// if done *perfectly* this coud be live-traced
// essentially by always heading a few steps ahead
// But we are going for MC compilation, so thats not an option either :c
Directory.CreateDirectory("./Output/");
Directory.CreateDirectory("./Output/generated/");
EntityStack = new EntityStack();
JumpTargets = new Dictionary<Instruction, string>();
// Kick it off my dude
ProcessFunction("main", assembly.MainModule.EntryPoint.Body.Instructions.First());
}
private int FunctionId = 0;
private string ProcessFunction(string name, Instruction firstInstruction, List<Instruction> trace = null, bool IsLoopHead = false, List<Entity> Locals = null, int LocalsId = 0)
{
var funcName = $"generated:{name}";
JumpTargets.Add(firstInstruction, funcName);
int subAmount = 0;
int MyFuncId = ++FunctionId;
var prepend = $"[{name}|{MyFuncId}]";
var builder = new StringBuilder();
if (Locals == null)
Locals = new List<Entity>();
if (trace == null)
trace = new List<Instruction>();
string CallSub(Instruction start)
{
return ProcessFunction(name + "b" + ++subAmount, start, trace, !IsLoopHead, Locals, LocalsId);
}
Entity Push(SupportedType type)
{
Debug($"Pushed {type}");
var v = EntityStack.PushOne(type, out var cmd);
AppendLine(cmd);
return v;
}
Entity Pop()
{
Debug("Popped");
return EntityStack.PopOne();
}
Entity Local(int index, SupportedType type)
{
Debug($"Created Local at {index}");
while (index + 1 > Locals.Count)
Locals.Add(null);
if (Locals[index] == null)
{
var v = new Entity(++LocalsId, $"var_{MyFuncId}", type);
var tag = $"var_{MyFuncId}_{LocalsId}";
AppendLine(Entity.Summon(tag));
Locals[index] = v;
return v;
}
else
return Locals[index];
}
void Debug(string msg)
{
clrLogger.Debug(prepend + msg);
}
void AppendLine(string content = "")
{
Debug(content);
builder.AppendLine(content);
}
Debug($"Processing {name}");
var instruction = firstInstruction;
do
{
var opCode = instruction.OpCode;
Debug(opCode.Name);
if (opCode == OpCodes.Nop)
{
AppendLine("# Nop - Mostly seen in DEBUG mode!");
}
#region ldc.i4
else if (opCode == OpCodes.Ldc_I4_0)
{
var v = Push(SupportedType.Int32);
AppendLine(v.SetValue(0));
}
else if (opCode == OpCodes.Ldc_I4_1)
{
var v = Push(SupportedType.Int32);
AppendLine(v.SetValue(1));
}
else if (opCode == OpCodes.Ldc_I4_2)
{
var v = Push(SupportedType.Int32);
AppendLine(v.SetValue(2));
}
else if (opCode == OpCodes.Ldc_I4_3)
{
var v = Push(SupportedType.Int32);
AppendLine(v.SetValue(3));
}
else if (opCode == OpCodes.Ldc_I4_4)
{
var v = Push(SupportedType.Int32);
AppendLine(v.SetValue(4));
}
else if (opCode == OpCodes.Ldc_I4_5)
{
var v = Push(SupportedType.Int32);
AppendLine(v.SetValue(5));
}
else if (opCode == OpCodes.Ldc_I4_6)
{
var v = Push(SupportedType.Int32);
AppendLine(v.SetValue(6));
}
else if (opCode == OpCodes.Ldc_I4_7)
{
var v = Push(SupportedType.Int32);
AppendLine(v.SetValue(7));
}
else if (opCode == OpCodes.Ldc_I4_8)
{
var v = Push(SupportedType.Int32);
AppendLine(v.SetValue(8));
}
else if (opCode == OpCodes.Ldc_I4_S)
{
var v = Push(SupportedType.Int32);
AppendLine(v.SetValue((sbyte)instruction.Operand));
}
#endregion
#region stloc
else if (opCode == OpCodes.Stloc_0)
{
var v = Pop();
AppendLine(Local(0, v.Type).SetValue(v));
}
else if (opCode == OpCodes.Stloc_1)
{
var v = Pop();
AppendLine(Local(1, v.Type).SetValue(v));
}
else if (opCode == OpCodes.Stloc_2)
{
var v = Pop();
AppendLine(Local(2, v.Type).SetValue(v));
}
else if (opCode == OpCodes.Stloc_3)
{
var v = Pop();
AppendLine(Local(3, v.Type).SetValue(v));
}
else if (opCode == OpCodes.Stloc_S)
{
var v = Pop();
AppendLine(Local((byte)instruction.Operand, v.Type).SetValue(v));
}
#endregion
#region ldloc
else if (opCode == OpCodes.Ldloc_0)
{
var v = Locals[0];
Push(v.Type).SetValue(v);
}
else if (opCode == OpCodes.Ldloc_1)
{
var v = Locals[1];
Push(v.Type).SetValue(v);
}
else if (opCode == OpCodes.Ldloc_2)
{
var v = Locals[2];
Push(v.Type).SetValue(v);
}
else if (opCode == OpCodes.Ldloc_3)
{
var v = Locals[3];
Push(v.Type).SetValue(v);
}
else if (opCode == OpCodes.Ldloc_S)
{
var v = Locals[(byte)instruction.Operand];
Push(v.Type).SetValue(v);
}
#endregion
// FUCK BRANCHES I HATE MY LIFE SO MUCH
#region Branches
else if (opCode == OpCodes.Br_S)
{
var target = (Instruction)instruction.Operand;
if (!JumpTargets.TryGetValue(target, out string func))
{
func = CallSub(target);
}
AppendLine($"function {func}");
}
else if (opCode == OpCodes.Brtrue_S)
{
var v = Pop();
var target = (Instruction)instruction.Operand;
if (!JumpTargets.TryGetValue(target, out string func))
{
func = CallSub(target);
}
AppendLine($"execute unless entity {v.GetSelector($"scores={{{Scoreboards.VALUE} = 0}}")} run function {func}");
}
#endregion
#region Comparer
else if (opCode == OpCodes.Cgt)
{
var v1 = Pop();
var v2 = Pop();
builder.AppendLine($"execute store result score {Push(SupportedType.Int32).Selector} __value run scoreboard players operation {v1.Selector} __value > {v2.Selector} __value");
}
else if (opCode == OpCodes.Ceq)
{
var v1 = Pop();
var v2 = Pop();
builder.AppendLine($"execute store result score {Push(SupportedType.Int32).Selector} __value run scoreboard players operation {v1.Selector} __value = {v2.Selector} __value");
}
#endregion
#region Operators
else if (opCode == OpCodes.Add)
{
var v1 = Pop();
var v2 = Pop();
var v3 = Push(SupportedType.Int32);
AppendLine(v3.SetValue(v2));
builder.AppendLine($"scoreboard players operation {v3.Selector} __value += {v2.Selector} __value");
}
else if (opCode == OpCodes.Sub)
{
var v1 = Pop();
var v2 = Pop();
var v3 = Push(SupportedType.Int32);
AppendLine(v3.SetValue(v2));
builder.AppendLine($"scoreboard players operation {v3.Selector} __value -= {v2.Selector} __value");
}
else if (opCode == OpCodes.Div)
{
var v1 = Pop();
var v2 = Pop();
var v3 = Push(SupportedType.Int32);
AppendLine(v3.SetValue(v2));
builder.AppendLine($"scoreboard players operation {v3.Selector} __value /= {v2.Selector} __value");
}
else if (opCode == OpCodes.Mul)
{
var v1 = Pop();
var v2 = Pop();
var v3 = Push(SupportedType.Int32);
AppendLine(v3.SetValue(v2));
builder.AppendLine($"scoreboard players operation {v3.Selector} __value *= {v2.Selector} __value");
}
#endregion
else if (opCode == OpCodes.Call)
{
var arg = (MethodReference)instruction.Operand;
if (arg.FullName == "System.Void System.Console::WriteLine(System.String)")
{
var arg1 = Pop();
AppendLine($"say {arg1.Selector}");
}
else
AppendLine($"# Coudnt Register Call {arg.FullName}");
}
else if (opCode == OpCodes.Ldstr)
{
var str = (string)instruction.Operand;
EntityStack.PushOne(str, out var command);
AppendLine(command);
}
else if (opCode == OpCodes.Ret)
{
AppendLine($"# Return, i hope there is nothing after this ^^");
break;
}
else
AppendLine($"# Coudnt Process OpCode: {instruction}");
builder.Append(EntityStack.Cleanup());
trace.Add(instruction);
instruction = instruction.Next;
if (trace.Contains(instruction))
{
if (JumpTargets.TryGetValue(instruction, out string file))
{
AppendLine($"function {file}");
}
break;
}
} while (true);
File.WriteAllText($"./Output/generated/{name}.mcfunction", builder.ToString());
return funcName;
}
private void ProcessExportedTypes()
{
foreach (var exportedType in assembly.MainModule.GetTypes())
{
if (FullSymbolToTypeRef.ContainsKey(exportedType.FullName))
continue;
// Handle for Struct
var structDef = ProcessForStruct(exportedType);
FullSymbolToTypeRef.Add(exportedType.FullName, structDef);
}
}
private StructDefinition ProcessForStruct(TypeDefinition type)
{
clrLogger.Debug($"Processing {type.FullName} type for LLVM");
var processed = ResolveAllFields(type.Resolve());
foreach (var field in processed)
{
if (field.FieldType.IsNested && !FullSymbolToTypeRef.ContainsKey(field.FieldType.FullName))
{
FullSymbolToTypeRef.Add(field.FieldType.FullName, ProcessForStruct(field.FieldType.Resolve()));
}
}
var structDef = new StructDefinition
{
CS_StructName = type.FullName,
CS_FieldDefs = processed.ToList(),
};
return structDef;
}
private FieldDefinition[] ResolveAllFields(TypeDefinition type)
{
var Fields = new Stack<FieldDefinition>(); // Depth-First my Dudes
foreach (var field in type.Fields)
{
Fields.Push(field);
}
if (type.BaseType != null && type.FullName != type.BaseType.FullName)
{
var declaredTypeFields = ResolveAllFields(type.BaseType.Resolve());
foreach (var field in declaredTypeFields)
{
Fields.Push(field);
}
}
return Fields.ToArray();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment