Created
September 26, 2018 12:43
-
-
Save HurricanKai/e04895df3b1959e32b6fb3e3dffef2ed to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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