public static void SUBN(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { Label CaseNotOverflow = gen.DefineLabel(); Label Continue = gen.DefineLabel(); gen.EmitGetRegister(inst.Value.Reg0, ctx); gen.EmitGetRegister(inst.Value.Reg1, ctx); gen.Emit(OpCodes.Ldind_I1); gen.Emit(OpCodes.Conv_U1); gen.EmitGetRegister(inst.Value.Reg0, ctx); gen.Emit(OpCodes.Ldind_I1); gen.Emit(OpCodes.Conv_U1); gen.Emit(OpCodes.Sub); //now the stack contains the difference and the register address gen.Emit(OpCodes.Dup); gen.Emit(OpCodes.Ldc_I4_M1); gen.Emit(OpCodes.Bgt, CaseNotOverflow); //CaseOverflow gen.EmitGetRegister(0xF, ctx); gen.Emit(OpCodes.Ldc_I4, 0); gen.Emit(OpCodes.Br, Continue); gen.MarkLabel(CaseNotOverflow); gen.EmitGetRegister(0xF, ctx); gen.Emit(OpCodes.Ldc_I4, 1); //Assign value to VF and store sum gen.MarkLabel(Continue); gen.Emit(OpCodes.Stind_I1); //Store the VF value that's on the stack gen.Emit(OpCodes.Conv_U1); //Store the difference gen.Emit(OpCodes.Stind_I1); }
public static void LRI(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldc_I4, inst.Value.Immediate16); //gen.Emit(OpCodes.Conv_I2); gen.Emit(OpCodes.Call, ctx.RegisterI.SetMethod); }
public static void XOR(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { gen.EmitPushRegistersAndIndirectTarget(inst.Value.Reg0, inst.Value.Reg1, ctx); gen.Emit(OpCodes.Xor); gen.Emit(OpCodes.Conv_U1); gen.Emit(OpCodes.Stind_I1); }
public static void SHL(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { Label Continue = gen.DefineLabel(); Label HighBitNotSet = gen.DefineLabel(); gen.EmitGetRegister(inst.Value.Reg0, ctx); gen.Emit(OpCodes.Dup); //Duplicate address for loading gen.Emit(OpCodes.Ldind_U1); //Load value gen.Emit(OpCodes.Dup); //duplicate value for VF comparison gen.Emit(OpCodes.Stloc_0); //Store for VF comparison gen.Emit(OpCodes.Ldc_I4_1); gen.Emit(OpCodes.Shl); //Shift and store gen.Emit(OpCodes.Stind_I1); gen.Emit(OpCodes.Ldloc_0); gen.Emit(OpCodes.Ldc_I4, 0x80); gen.Emit(OpCodes.And); gen.Emit(OpCodes.Ldc_I4_0); gen.Emit(OpCodes.Beq, HighBitNotSet); //HighBitSet: gen.EmitGetRegister(0xF, ctx); gen.Emit(OpCodes.Ldc_I4_1); gen.Emit(OpCodes.Br, Continue); //Not set gen.MarkLabel(HighBitNotSet); gen.EmitGetRegister(0xF, ctx); gen.Emit(OpCodes.Ldc_I4_0); gen.MarkLabel(Continue); gen.Emit(OpCodes.Stind_I1); }
public static void ADD(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { Label CaseNotOverflow = gen.DefineLabel(); Label Continue = gen.DefineLabel(); gen.EmitPushRegistersAndIndirectTarget(inst.Value.Reg0, inst.Value.Reg1, ctx); gen.Emit(OpCodes.Add); //now the stack contains the sum and the register address gen.Emit(OpCodes.Dup); //Duplicate the sum for comparison gen.Emit(OpCodes.Ldc_I4, 0xFF); gen.Emit(OpCodes.Ble, CaseNotOverflow); //If greater goto case overflow //else CaseOverflow: gen.EmitGetRegister(0xF, ctx); //Push VF register addres for storing the overflow bit gen.Emit(OpCodes.Ldc_I4, 1); gen.Emit(OpCodes.Br, Continue); //CaseNotOverflow: gen.MarkLabel(CaseNotOverflow); gen.EmitGetRegister(0xF, ctx); //Push VF register addres for storing the overflow bit gen.Emit(OpCodes.Ldc_I4, 0); //Assign value to VF and store sum gen.MarkLabel(Continue); gen.Emit(OpCodes.Stind_I1); //Store the VF value that's on the stack gen.Emit(OpCodes.Conv_U1); //Store the sum value we duplicated earlier gen.Emit(OpCodes.Stind_I1); }
public static void LD(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { gen.EmitGetRegister(inst.Value.Reg0, ctx); //Push target address gen.EmitGetRegister(inst.Value.Reg1, ctx); //Push source address gen.Emit(OpCodes.Ldind_U1); //Pop source address, push value gen.Emit(OpCodes.Stind_I1); //assign value }
public static void SDT(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { gen.EmitGetRegister(inst.Value.Reg0, ctx); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Call, ctx.RegisterDT.GetMethod); gen.Emit(OpCodes.Stind_I1); }
public static void IN(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { Debug.WriteLine("WARN: Unimplemented input instruction"); //Wait for a key press, store the value of the key in Vx. gen.EmitGetRegister(inst.Value.Reg0, ctx); gen.Emit(OpCodes.Ldc_I4_0); gen.Emit(OpCodes.Stind_I1); }
public static void SEI(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { gen.EmitGetRegister(inst.Value.Reg0, ctx); gen.Emit(OpCodes.Ldind_I1); gen.Emit(OpCodes.Conv_U1); gen.EmitLoadImmediate(inst); gen.Emit(OpCodes.Beq, ctx.Label(inst.Offset + 4)); }
public static void CLS(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { var method = typeof(Interpreter.Implementation).GetMethod(nameof(Implementation.CLS)); //Args are: Chip8State state gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Call, method); }
public static void STBCD(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { var method = typeof(Interpreter.Implementation).GetMethod(nameof(Implementation.STBCD)); //Args are: Chip8State state, ref byte regx gen.Emit(OpCodes.Ldarg_0); gen.EmitGetRegister(inst.Value.Reg0, ctx); gen.Emit(OpCodes.Call, method); }
public static void LDREG(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { var method = typeof(Interpreter.Implementation).GetMethod(nameof(Implementation.LDREG)); //Args are: Chip8State state, byte regx gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldc_I4_S, inst.Value.Reg0); gen.Emit(OpCodes.Call, method); }
public static void ADDI(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { gen.EmitGetRegister(inst.Value.Reg0, ctx); gen.Emit(OpCodes.Dup); gen.Emit(OpCodes.Ldind_U1); gen.EmitLoadImmediate(inst); gen.Emit(OpCodes.Add); gen.Emit(OpCodes.Conv_U1); gen.Emit(OpCodes.Stind_I1); }
public static void SNER(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { gen.EmitGetRegister(inst.Value.Reg0, ctx); gen.Emit(OpCodes.Ldind_I1); gen.Emit(OpCodes.Conv_U1); gen.EmitGetRegister(inst.Value.Reg1, ctx); gen.Emit(OpCodes.Ldind_I1); gen.Emit(OpCodes.Conv_U1); gen.Emit(OpCodes.Bne_Un, ctx.Label(inst.Offset + 4)); }
public static void JMP(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { if (inst.Instr.Value.Immediate16 == inst.Offset) { gen.Emit(OpCodes.Ret); } else { gen.Emit(OpCodes.Br, ctx.Labels[inst.Value.Immediate16]); } }
public static void ADDRI(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { gen.Emit(OpCodes.Ldarg_0); //reference for the last store call gen.EmitGetRegister(inst.Value.Reg0, ctx); gen.Emit(OpCodes.Ldind_I1); gen.Emit(OpCodes.Conv_U1); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Call, ctx.RegisterI.GetMethod); gen.Emit(OpCodes.Add); gen.Emit(OpCodes.Call, ctx.RegisterI.SetMethod); }
public static void DRW(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { var method = typeof(Interpreter.Implementation).GetMethod(nameof(Implementation.DRW)); //Args are: Chip8State state, ref byte regx, ref byte regy, byte imm4 gen.Emit(OpCodes.Ldarg_0); gen.EmitGetRegister(inst.Value.Reg0, ctx); gen.EmitGetRegister(inst.Value.Reg1, ctx); gen.Emit(OpCodes.Ldc_I4_S, inst.Value.Immediate4); gen.Emit(OpCodes.Call, method); }
private (DynamicMethod, ILGenerator) CreateMethod(string name, JITContext ctx) { DynamicMethod res = (DynamicMethod)(object)new DynamicMethod(name, typeof(void), new[] { typeof(Chip8State), typeof(Registers).MakeByRefType() }); ILGenerator gen = res.GetILGenerator(); gen.DeclareLocal(typeof(int)); return(res, gen); }
public static void JMP0(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { List <Label> JumpTable = new List <Label>(); for (int i = inst.Value.Immediate16; i < inst.Value.Immediate16 + 256; i++) { JumpTable.Add(ctx.Label(i)); } gen.EmitGetRegister(0, ctx); gen.Emit(OpCodes.Ldind_I1); gen.Emit(OpCodes.Conv_U1); gen.Emit(OpCodes.Switch, JumpTable.ToArray()); }
public JITROMDelegate JITROM(Span <byte> ROM, SortedSet <UInt16> BreakPoints = null) { if (BreakPoints == null) { BreakPoints = new SortedSet <ushort>(); } var exe = disasm.DisassembleProgram(ROM); JITContext ctx = new JITContext { BreakPoints = BreakPoints, Disasm = exe }; var(res, gen) = CreateMethod("JITROM", ctx); //Find all function calls Dictionary <UInt16, TranslatedFunction> Functions = exe.Entries.Where(x => !x.IsData && x.Instr.Value.Instruction == Instruction.CALL) .Select(x => x.Instr.Value.Immediate16) .Distinct() .ToDictionary(x => x, x => (TranslatedFunction)null); //Function calls will get translated to their own method to use the runtime call stack foreach (var f in Functions.Keys.ToArray()) { var(tmpMethod, TmpGen) = CreateMethod("method_" + f.ToString("X4"), ctx); Functions[f] = new TranslatedFunction { Method = tmpMethod, Generator = TmpGen, Info = tmpMethod }; } ctx.Functions = Functions; //As detecting where a funnction ends is not trivial we're just going to JIT all functions to the end of the rom, disgusting i know foreach (var f in Functions.Keys.Where(x => x >= Chip8State.ProgramStart)) { JITBlock(Functions[f].Generator, ctx, (f - Chip8State.ProgramStart) / 2, exe.Entries.Length); } //Then JIT the whole ROM JITBlock(gen, ctx, 0, exe.Entries.Length); return((JITROMDelegate)res.CreateDelegate(typeof(JITROMDelegate))); }
public static void SHR(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { gen.EmitGetRegister(inst.Value.Reg0, ctx); gen.Emit(OpCodes.Dup); //Duplicate address for loading gen.Emit(OpCodes.Ldind_U1); //Load value gen.Emit(OpCodes.Dup); //duplicate value for VF comparison gen.Emit(OpCodes.Stloc_0); //Store for VF comparison gen.Emit(OpCodes.Ldc_I4_1); gen.Emit(OpCodes.Shr_Un); //Shift and store gen.Emit(OpCodes.Stind_I1); gen.EmitGetRegister(0xF, ctx); gen.Emit(OpCodes.Ldloc_0); gen.Emit(OpCodes.Ldc_I4_1); gen.Emit(OpCodes.And); gen.Emit(OpCodes.Stind_I1); }
public static void RET(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { gen.Emit(OpCodes.Ret); }
/// <summary> /// Pushes the address of the N register on the stack /// </summary> internal static void EmitGetRegister(this ILGenerator gen, int N, JITContext ctx) { gen.Emit(OpCodes.Ldarg_1); gen.Emit(OpCodes.Ldflda, Registers.Fields[N]); }
public static void SYS(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { Debug.WriteLine("WARN: Sys called"); }
public static void CALL(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldarg_1); gen.Emit(OpCodes.Call, ctx.Functions[inst.Value.Immediate16].Method); }
public static void SKNP(JITContext ctx, Disassembler.DecompEntry inst, ILGenerator gen) { Debug.WriteLine("WARN: Unimplemented input instruction"); //Skip next instruction if key with the value of Vx is pressed. }
/// <summary> /// After the code emitted by this method the managed stack looks like: /// /// <br>--bottom--</br> /// <br>target register address</br> /// <br>target value </br> /// <br>source value </br> /// /// </summary> /// <param name="target">target register index</param> /// <param name="source">source register index</param> /// <param name="ctx">state ctx</param> internal static void EmitPushRegistersAndIndirectTarget(this ILGenerator gen, int target, int source, JITContext ctx) { gen.EmitGetRegister(target, ctx); gen.Emit(OpCodes.Dup); gen.Emit(OpCodes.Ldind_U1); gen.EmitGetRegister(source, ctx); gen.Emit(OpCodes.Ldind_U1); }
void JITBlock(ILGenerator gen, JITContext ctx, int start, int end) { //Every function has its own set of labels ctx.Labels = new Dictionary <ushort, Label>(); void AddLabel(int v) { //Is this offset outside of the ROM ? Sometimes data can be misinterpreted as code if (v < Chip8State.ProgramStart) { return; } //Are we trying to jump from a function to code outside of it ? if ((v - Chip8State.ProgramStart) / 2 < start) { Debugger.Break(); } if (!ctx.Labels.ContainsKey((UInt16)v)) { ctx.Labels.Add((UInt16)v, gen.DefineLabel()); } } //Calculate all the labels we need beforehand for (int i = start; i < end; i++) { var entry = ctx.Disasm.Entries[i]; if (entry.IsData) { continue; } if (!entry.Value.IsControlFlow() || entry.Instruction == Instruction.RET) { continue; } if (entry.Value.IsSkipNext()) { AddLabel(entry.Offset + 4); } else if (entry.Value.IsJumpStatic()) { AddLabel(entry.Value.Immediate16); } else if (entry.Instr.Value.IsJumpVariable()) { /* * The JMP0 instruction jumps to immediate value + V0 * The optimal way to implement this would be hooking jumps and JIT based on the address... * ...Or we can bruteforce all 256 cases by using a jumptable with the MSIL "switch" instruction */ for (int j = entry.Value.Immediate16; j < entry.Instr.Value.Immediate16 + 256; j++) { AddLabel(j); } } else { throw new Exception("Invalid jump instruction"); } } SortedSet <UInt16> MarkedLabels = new SortedSet <ushort>(); for (int i = start; i < end; i++) { var entry = ctx.Disasm.Entries[i]; if (entry.IsData) { continue; } var instr = entry.Instr.Value.Instruction; //This is not really useful as visual studio can't debug "Lightweight functions" if (ctx.BreakPoints.Contains(entry.Offset)) { gen.Emit(OpCodes.Break); } //Mark labels as we go if (ctx.Labels.ContainsKey(entry.Offset)) { gen.MarkLabel(ctx.Labels[entry.Offset]); MarkedLabels.Add(entry.Offset); } LinkEmitters[instr](ctx, entry, gen); } //Set all other labels to end of the fnction foreach (var lbl in ctx.Labels) { if (!MarkedLabels.Contains(lbl.Key)) { gen.MarkLabel(lbl.Value); } } gen.Emit(OpCodes.Ret); }