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 long _subPosition; private int _opcIndex; private Block _currBlock; public Block CurrBlock => _currBlock; public OpCode64 CurrOp => _currBlock?.OpCodes[_opcIndex]; private Dictionary _visitedBlocks; private Queue _branchTargets; private List _ilBlocks; 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 IntTmpIndex = -1; private const int RorTmpIndex = -2; private const int CmpOptTmp1Index = -3; private const int CmpOptTmp2Index = -4; private const int VecTmp1Index = -5; private const int VecTmp2Index = -6; public ILEmitterCtx(TranslatorCache cache, Block graph) { _cache = cache ?? throw new ArgumentNullException(nameof(cache)); _currBlock = graph ?? throw new ArgumentNullException(nameof(graph)); _labels = new Dictionary(); _visitedBlocks = new Dictionary(); _visitedBlocks.Add(graph, new ILBlock()); _branchTargets = new Queue(); _ilBlocks = new List(); _subPosition = graph.Position; ResetBlockState(); AdvanceOpCode(); } public ILBlock[] GetILBlocks() { EmitAllOpCodes(); return _ilBlocks.ToArray(); } private void EmitAllOpCodes() { do { EmitOpCode(); } while (AdvanceOpCode()); } private void EmitOpCode() { if (_currBlock == null) { return; } 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); } private bool AdvanceOpCode() { if (_currBlock == null) { return false; } while (++_opcIndex >= _currBlock.OpCodes.Count) { if (!AdvanceBlock()) { return false; } ResetBlockState(); } return true; } private bool AdvanceBlock() { if (_currBlock.Branch != null) { if (_visitedBlocks.TryAdd(_currBlock.Branch, _ilBlock.Branch)) { _branchTargets.Enqueue(_currBlock.Branch); } } if (_currBlock.Next != null) { if (_visitedBlocks.TryAdd(_currBlock.Next, _ilBlock.Next)) { _currBlock = _currBlock.Next; return true; } else { Emit(OpCodes.Br, GetLabel(_currBlock.Next.Position)); } } return _branchTargets.TryDequeue(out _currBlock); } private void ResetBlockState() { _ilBlock = _visitedBlocks[_currBlock]; _ilBlocks.Add(_ilBlock); _ilBlock.Next = GetOrCreateILBlock(_currBlock.Next); _ilBlock.Branch = GetOrCreateILBlock(_currBlock.Branch); _opcIndex = -1; _optOpLastFlagSet = null; _optOpLastCompare = null; } private ILBlock GetOrCreateILBlock(Block block) { if (block == null) { return null; } if (_visitedBlocks.TryGetValue(block, out ILBlock ilBlock)) { return ilBlock; } return new ILBlock(); } 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.SubArgs) { 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(_subPosition); return true; } public void TryOptMarkCondWithoutCmp() { _optOpLastCompare = CurrOp; InstEmitAluHelper.EmitDataLoadOpers(this); Stloc(CmpOptTmp2Index, IoType.Int); Stloc(CmpOptTmp1Index, 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) { OpCode ilOp; int intCond = (int)cond; if (_optOpLastCompare != null && _optOpLastCompare == _optOpLastFlagSet && _branchOps.ContainsKey(cond)) { Ldloc(CmpOptTmp1Index, IoType.Int, _optOpLastCompare.RegisterSize); Ldloc(CmpOptTmp2Index, 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, OpCode ilOp) { if (amount > 0) { EmitLdc_I4(amount); Emit(ilOp); } } public void EmitRor(int amount) { if (amount > 0) { Stloc(RorTmpIndex, IoType.Int); Ldloc(RorTmpIndex, IoType.Int); EmitLdc_I4(amount); Emit(OpCodes.Shr_Un); Ldloc(RorTmpIndex, 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(OpCode ilOp) { _ilBlock.Add(new ILOpCode(ilOp)); } public void 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() { if (_ilBlock.Next == null) { throw new InvalidOperationException("Can't load state for next block, because there's no next block."); } _ilBlock.Add(new ILOpCodeLoadState(_ilBlock.Next)); } public void EmitStoreState() { _ilBlock.Add(new ILOpCodeStoreState(_ilBlock)); } public void EmitLdtmp() => EmitLdint(IntTmpIndex); public void EmitSttmp() => EmitStint(IntTmpIndex); public void EmitLdvectmp() => EmitLdvec(VecTmp1Index); public void EmitStvectmp() => EmitStvec(VecTmp1Index); public void EmitLdvectmp2() => EmitLdvec(VecTmp2Index); public void EmitStvectmp2() => EmitStvec(VecTmp2Index); 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(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); } } }