using ChocolArm64.Decoders; using ChocolArm64.Instructions; using ChocolArm64.State; using System; using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; namespace ChocolArm64.Translation { class ILEmitterCtx { private TranslatorCache _cache; private Dictionary _labels; private int _blkIndex; private int _opcIndex; private Block[] _graph; private Block _root; public Block CurrBlock => _graph[_blkIndex]; public OpCode64 CurrOp => _graph[_blkIndex].OpCodes[_opcIndex]; private ILEmitter _emitter; private ILBlock _ilBlock; private OpCode64 _optOpLastCompare; private OpCode64 _optOpLastFlagSet; //This is the index of the temporary register, used to store temporary //values needed by some functions, since IL doesn't have a swap instruction. //You can use any value here as long it doesn't conflict with the indices //for the other registers. Any value >= 64 or < 0 will do. private const int Tmp1Index = -1; private const int Tmp2Index = -2; private const int Tmp3Index = -3; private const int Tmp4Index = -4; private const int Tmp5Index = -5; private const int Tmp6Index = -6; public ILEmitterCtx( TranslatorCache cache, Block[] graph, Block root, string subName) { _cache = cache ?? throw new ArgumentNullException(nameof(cache)); _graph = graph ?? throw new ArgumentNullException(nameof(graph)); _root = root ?? throw new ArgumentNullException(nameof(root)); _labels = new Dictionary(); _emitter = new ILEmitter(graph, root, subName); _ilBlock = _emitter.GetIlBlock(0); _opcIndex = -1; if (graph.Length == 0 || !AdvanceOpCode()) { throw new ArgumentException(nameof(graph)); } } public TranslatedSub GetSubroutine() { return _emitter.GetSubroutine(); } public bool AdvanceOpCode() { if (_opcIndex + 1 == CurrBlock.OpCodes.Count && _blkIndex + 1 == _graph.Length) { return false; } while (++_opcIndex >= (CurrBlock?.OpCodes.Count ?? 0)) { _blkIndex++; _opcIndex = -1; _optOpLastFlagSet = null; _optOpLastCompare = null; _ilBlock = _emitter.GetIlBlock(_blkIndex); } return true; } public void EmitOpCode() { if (_opcIndex == 0) { MarkLabel(GetLabel(CurrBlock.Position)); EmitSynchronization(); } CurrOp.Emitter(this); _ilBlock.Add(new ILBarrier()); } private void EmitSynchronization() { EmitLdarg(TranslatedSub.StateArgIdx); EmitLdc_I4(CurrBlock.OpCodes.Count); EmitPrivateCall(typeof(CpuThreadState), nameof(CpuThreadState.Synchronize)); EmitLdc_I4(0); ILLabel lblContinue = new ILLabel(); Emit(OpCodes.Bne_Un_S, lblContinue); EmitLdc_I8(0); Emit(OpCodes.Ret); MarkLabel(lblContinue); } public bool TryOptEmitSubroutineCall() { if (CurrBlock.Next == null) { return false; } if (CurrOp.Emitter != InstEmit.Bl) { return false; } if (!_cache.TryGetSubroutine(((OpCodeBImmAl64)CurrOp).Imm, out TranslatedSub subroutine)) { return false; } for (int index = 0; index < TranslatedSub.FixedArgTypes.Length; index++) { EmitLdarg(index); } foreach (Register reg in subroutine.Params) { switch (reg.Type) { case RegisterType.Flag: Ldloc(reg.Index, IoType.Flag); break; case RegisterType.Int: Ldloc(reg.Index, IoType.Int); break; case RegisterType.Vector: Ldloc(reg.Index, IoType.Vector); break; } } EmitCall(subroutine.Method); subroutine.AddCaller(_root.Position); return true; } public void TryOptMarkCondWithoutCmp() { _optOpLastCompare = CurrOp; InstEmitAluHelper.EmitDataLoadOpers(this); Stloc(Tmp4Index, IoType.Int); Stloc(Tmp3Index, IoType.Int); } private Dictionary _branchOps = new Dictionary() { { Cond.Eq, OpCodes.Beq }, { Cond.Ne, OpCodes.Bne_Un }, { Cond.GeUn, OpCodes.Bge_Un }, { Cond.LtUn, OpCodes.Blt_Un }, { Cond.GtUn, OpCodes.Bgt_Un }, { Cond.LeUn, OpCodes.Ble_Un }, { Cond.Ge, OpCodes.Bge }, { Cond.Lt, OpCodes.Blt }, { Cond.Gt, OpCodes.Bgt }, { Cond.Le, OpCodes.Ble } }; public void EmitCondBranch(ILLabel target, Cond cond) { System.Reflection.Emit.OpCode ilOp; int intCond = (int)cond; if (_optOpLastCompare != null && _optOpLastCompare == _optOpLastFlagSet && _branchOps.ContainsKey(cond)) { Ldloc(Tmp3Index, IoType.Int, _optOpLastCompare.RegisterSize); Ldloc(Tmp4Index, IoType.Int, _optOpLastCompare.RegisterSize); ilOp = _branchOps[cond]; } else if (intCond < 14) { int condTrue = intCond >> 1; switch (condTrue) { case 0: EmitLdflg((int)PState.ZBit); break; case 1: EmitLdflg((int)PState.CBit); break; case 2: EmitLdflg((int)PState.NBit); break; case 3: EmitLdflg((int)PState.VBit); break; case 4: EmitLdflg((int)PState.CBit); EmitLdflg((int)PState.ZBit); Emit(OpCodes.Not); Emit(OpCodes.And); break; case 5: case 6: EmitLdflg((int)PState.NBit); EmitLdflg((int)PState.VBit); Emit(OpCodes.Ceq); if (condTrue == 6) { EmitLdflg((int)PState.ZBit); Emit(OpCodes.Not); Emit(OpCodes.And); } break; } ilOp = (intCond & 1) != 0 ? OpCodes.Brfalse : OpCodes.Brtrue; } else { ilOp = OpCodes.Br; } Emit(ilOp, target); } public void EmitCast(IntType intType) { switch (intType) { case IntType.UInt8: Emit(OpCodes.Conv_U1); break; case IntType.UInt16: Emit(OpCodes.Conv_U2); break; case IntType.UInt32: Emit(OpCodes.Conv_U4); break; case IntType.UInt64: Emit(OpCodes.Conv_U8); break; case IntType.Int8: Emit(OpCodes.Conv_I1); break; case IntType.Int16: Emit(OpCodes.Conv_I2); break; case IntType.Int32: Emit(OpCodes.Conv_I4); break; case IntType.Int64: Emit(OpCodes.Conv_I8); break; } bool sz64 = CurrOp.RegisterSize != RegisterSize.Int32; if (sz64 == (intType == IntType.UInt64 || intType == IntType.Int64)) { return; } if (sz64) { Emit(intType >= IntType.Int8 ? OpCodes.Conv_I8 : OpCodes.Conv_U8); } else { Emit(OpCodes.Conv_U4); } } public void EmitLsl(int amount) => EmitIlShift(amount, OpCodes.Shl); public void EmitLsr(int amount) => EmitIlShift(amount, OpCodes.Shr_Un); public void EmitAsr(int amount) => EmitIlShift(amount, OpCodes.Shr); private void EmitIlShift(int amount, System.Reflection.Emit.OpCode ilOp) { if (amount > 0) { EmitLdc_I4(amount); Emit(ilOp); } } public void EmitRor(int amount) { if (amount > 0) { Stloc(Tmp2Index, IoType.Int); Ldloc(Tmp2Index, IoType.Int); EmitLdc_I4(amount); Emit(OpCodes.Shr_Un); Ldloc(Tmp2Index, IoType.Int); EmitLdc_I4(CurrOp.GetBitsCount() - amount); Emit(OpCodes.Shl); Emit(OpCodes.Or); } } public ILLabel GetLabel(long position) { if (!_labels.TryGetValue(position, out ILLabel output)) { output = new ILLabel(); _labels.Add(position, output); } return output; } public void MarkLabel(ILLabel label) { _ilBlock.Add(label); } public void Emit(System.Reflection.Emit.OpCode ilOp) { _ilBlock.Add(new ILOpCode(ilOp)); } public void Emit(System.Reflection.Emit.OpCode ilOp, ILLabel label) { _ilBlock.Add(new ILOpCodeBranch(ilOp, label)); } public void Emit(string text) { _ilBlock.Add(new ILOpCodeLog(text)); } public void EmitLdarg(int index) { _ilBlock.Add(new IlOpCodeLoad(index, IoType.Arg)); } public void EmitLdintzr(int index) { if (index != CpuThreadState.ZrIndex) { EmitLdint(index); } else { EmitLdc_I(0); } } public void EmitStintzr(int index) { if (index != CpuThreadState.ZrIndex) { EmitStint(index); } else { Emit(OpCodes.Pop); } } public void EmitLoadState(Block retBlk) { _ilBlock.Add(new IlOpCodeLoad(Array.IndexOf(_graph, retBlk), IoType.Fields)); } public void EmitStoreState() { _ilBlock.Add(new IlOpCodeStore(Array.IndexOf(_graph, CurrBlock), IoType.Fields)); } public void EmitLdtmp() => EmitLdint(Tmp1Index); public void EmitSttmp() => EmitStint(Tmp1Index); public void EmitLdvectmp() => EmitLdvec(Tmp5Index); public void EmitStvectmp() => EmitStvec(Tmp5Index); public void EmitLdvectmp2() => EmitLdvec(Tmp6Index); public void EmitStvectmp2() => EmitStvec(Tmp6Index); public void EmitLdint(int index) => Ldloc(index, IoType.Int); public void EmitStint(int index) => Stloc(index, IoType.Int); public void EmitLdvec(int index) => Ldloc(index, IoType.Vector); public void EmitStvec(int index) => Stloc(index, IoType.Vector); public void EmitLdflg(int index) => Ldloc(index, IoType.Flag); public void EmitStflg(int index) { _optOpLastFlagSet = CurrOp; Stloc(index, IoType.Flag); } private void Ldloc(int index, IoType ioType) { _ilBlock.Add(new IlOpCodeLoad(index, ioType, CurrOp.RegisterSize)); } private void Ldloc(int index, IoType ioType, RegisterSize registerSize) { _ilBlock.Add(new IlOpCodeLoad(index, ioType, registerSize)); } private void Stloc(int index, IoType ioType) { _ilBlock.Add(new IlOpCodeStore(index, ioType, CurrOp.RegisterSize)); } public void EmitCallPropGet(Type objType, string propName) { if (objType == null) { throw new ArgumentNullException(nameof(objType)); } if (propName == null) { throw new ArgumentNullException(nameof(propName)); } EmitCall(objType.GetMethod($"get_{propName}")); } public void EmitCallPropSet(Type objType, string propName) { if (objType == null) { throw new ArgumentNullException(nameof(objType)); } if (propName == null) { throw new ArgumentNullException(nameof(propName)); } EmitCall(objType.GetMethod($"set_{propName}")); } public void EmitCall(Type objType, string mthdName) { if (objType == null) { throw new ArgumentNullException(nameof(objType)); } if (mthdName == null) { throw new ArgumentNullException(nameof(mthdName)); } EmitCall(objType.GetMethod(mthdName)); } public void EmitPrivateCall(Type objType, string mthdName) { if (objType == null) { throw new ArgumentNullException(nameof(objType)); } if (mthdName == null) { throw new ArgumentNullException(nameof(mthdName)); } EmitCall(objType.GetMethod(mthdName, BindingFlags.Instance | BindingFlags.NonPublic)); } public void EmitCall(MethodInfo mthdInfo) { if (mthdInfo == null) { throw new ArgumentNullException(nameof(mthdInfo)); } _ilBlock.Add(new ILOpCodeCall(mthdInfo)); } public void EmitLdc_I(long value) { if (CurrOp.RegisterSize == RegisterSize.Int32) { EmitLdc_I4((int)value); } else { EmitLdc_I8(value); } } public void EmitLdc_I4(int value) { _ilBlock.Add(new ILOpCodeConst(value)); } public void EmitLdc_I8(long value) { _ilBlock.Add(new ILOpCodeConst(value)); } public void EmitLdc_R4(float value) { _ilBlock.Add(new ILOpCodeConst(value)); } public void EmitLdc_R8(double value) { _ilBlock.Add(new ILOpCodeConst(value)); } public void EmitZnFlagCheck() { EmitZnCheck(OpCodes.Ceq, (int)PState.ZBit); EmitZnCheck(OpCodes.Clt, (int)PState.NBit); } private void EmitZnCheck(System.Reflection.Emit.OpCode ilCmpOp, int flag) { Emit(OpCodes.Dup); Emit(OpCodes.Ldc_I4_0); if (CurrOp.RegisterSize != RegisterSize.Int32) { Emit(OpCodes.Conv_I8); } Emit(ilCmpOp); EmitStflg(flag); } } }