using ChocolArm64.Decoder; using ChocolArm64.Instruction; using ChocolArm64.State; using System; using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; namespace ChocolArm64.Translation { class AILEmitterCtx { private ATranslator Translator; private Dictionary Labels; private int BlkIndex; private int OpcIndex; private ABlock[] Graph; private ABlock Root; public ABlock CurrBlock => Graph[BlkIndex]; public AOpCode CurrOp => Graph[BlkIndex].OpCodes[OpcIndex]; private AILEmitter Emitter; private AILBlock ILBlock; private AOpCode OptOpLastCompare; private AOpCode 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; public AILEmitterCtx( ATranslator Translator, ABlock[] Graph, ABlock Root, string SubName) { if (Translator == null) { throw new ArgumentNullException(nameof(Translator)); } if (Graph == null) { throw new ArgumentNullException(nameof(Graph)); } if (Root == null) { throw new ArgumentNullException(nameof(Root)); } this.Translator = Translator; this.Graph = Graph; this.Root = Root; Labels = new Dictionary(); Emitter = new AILEmitter(Graph, Root, SubName); ILBlock = Emitter.GetILBlock(0); OpcIndex = -1; if (Graph.Length == 0 || !AdvanceOpCode()) { throw new ArgumentException(nameof(Graph)); } } public ATranslatedSub 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)); } CurrOp.Emitter(this); ILBlock.Add(new AILBarrier()); } public bool TryOptEmitSubroutineCall() { if (CurrBlock.Next == null) { return false; } if (!Translator.TryGetCachedSub(CurrOp, out ATranslatedSub Sub)) { return false; } for (int Index = 0; Index < ATranslatedSub.FixedArgTypes.Length; Index++) { EmitLdarg(Index); } foreach (ARegister Reg in Sub.Params) { switch (Reg.Type) { case ARegisterType.Flag: Ldloc(Reg.Index, AIoType.Flag); break; case ARegisterType.Int: Ldloc(Reg.Index, AIoType.Int); break; case ARegisterType.Vector: Ldloc(Reg.Index, AIoType.Vector); break; } } EmitCall(Sub.Method); Sub.AddCaller(Root.Position); return true; } public void TryOptMarkCondWithoutCmp() { OptOpLastCompare = CurrOp; AInstEmitAluHelper.EmitDataLoadOpers(this); Stloc(Tmp4Index, AIoType.Int); Stloc(Tmp3Index, AIoType.Int); } private Dictionary BranchOps = new Dictionary() { { ACond.Eq, OpCodes.Beq }, { ACond.Ne, OpCodes.Bne_Un }, { ACond.Ge_Un, OpCodes.Bge_Un }, { ACond.Lt_Un, OpCodes.Blt_Un }, { ACond.Gt_Un, OpCodes.Bgt_Un }, { ACond.Le_Un, OpCodes.Ble_Un }, { ACond.Ge, OpCodes.Bge }, { ACond.Lt, OpCodes.Blt }, { ACond.Gt, OpCodes.Bgt }, { ACond.Le, OpCodes.Ble } }; public void EmitCondBranch(AILLabel Target, ACond Cond) { OpCode ILOp; int IntCond = (int)Cond; if (OptOpLastCompare != null && OptOpLastCompare == OptOpLastFlagSet && BranchOps.ContainsKey(Cond)) { Ldloc(Tmp3Index, AIoType.Int, OptOpLastCompare.RegisterSize); Ldloc(Tmp4Index, AIoType.Int, OptOpLastCompare.RegisterSize); if (OptOpLastCompare.Emitter == AInstEmit.Adds) { Emit(OpCodes.Neg); } ILOp = BranchOps[Cond]; } else if (IntCond < 14) { int CondTrue = IntCond >> 1; switch (CondTrue) { case 0: EmitLdflg((int)APState.ZBit); break; case 1: EmitLdflg((int)APState.CBit); break; case 2: EmitLdflg((int)APState.NBit); break; case 3: EmitLdflg((int)APState.VBit); break; case 4: EmitLdflg((int)APState.CBit); EmitLdflg((int)APState.ZBit); Emit(OpCodes.Not); Emit(OpCodes.And); break; case 5: case 6: EmitLdflg((int)APState.NBit); EmitLdflg((int)APState.VBit); Emit(OpCodes.Ceq); if (CondTrue == 6) { EmitLdflg((int)APState.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(AIntType IntType) { switch (IntType) { case AIntType.UInt8: Emit(OpCodes.Conv_U1); break; case AIntType.UInt16: Emit(OpCodes.Conv_U2); break; case AIntType.UInt32: Emit(OpCodes.Conv_U4); break; case AIntType.UInt64: Emit(OpCodes.Conv_U8); break; case AIntType.Int8: Emit(OpCodes.Conv_I1); break; case AIntType.Int16: Emit(OpCodes.Conv_I2); break; case AIntType.Int32: Emit(OpCodes.Conv_I4); break; case AIntType.Int64: Emit(OpCodes.Conv_I8); break; } bool Sz64 = CurrOp.RegisterSize != ARegisterSize.Int32; if (Sz64 == (IntType == AIntType.UInt64 || IntType == AIntType.Int64)) { return; } if (Sz64) { Emit(IntType >= AIntType.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(Tmp2Index, AIoType.Int); Ldloc(Tmp2Index, AIoType.Int); EmitLdc_I4(Amount); Emit(OpCodes.Shr_Un); Ldloc(Tmp2Index, AIoType.Int); EmitLdc_I4(CurrOp.GetBitsCount() - Amount); Emit(OpCodes.Shl); Emit(OpCodes.Or); } } public AILLabel GetLabel(long Position) { if (!Labels.TryGetValue(Position, out AILLabel Output)) { Output = new AILLabel(); Labels.Add(Position, Output); } return Output; } public void MarkLabel(AILLabel Label) { ILBlock.Add(Label); } public void Emit(OpCode ILOp) { ILBlock.Add(new AILOpCode(ILOp)); } public void Emit(OpCode ILOp, AILLabel Label) { ILBlock.Add(new AILOpCodeBranch(ILOp, Label)); } public void Emit(string Text) { ILBlock.Add(new AILOpCodeLog(Text)); } public void EmitLdarg(int Index) { ILBlock.Add(new AILOpCodeLoad(Index, AIoType.Arg)); } public void EmitLdintzr(int Index) { if (Index != AThreadState.ZRIndex) { EmitLdint(Index); } else { EmitLdc_I(0); } } public void EmitStintzr(int Index) { if (Index != AThreadState.ZRIndex) { EmitStint(Index); } else { Emit(OpCodes.Pop); } } public void EmitLoadState(ABlock RetBlk) { ILBlock.Add(new AILOpCodeLoad(Array.IndexOf(Graph, RetBlk), AIoType.Fields)); } public void EmitStoreState() { ILBlock.Add(new AILOpCodeStore(Array.IndexOf(Graph, CurrBlock), AIoType.Fields)); } public void EmitLdtmp() => EmitLdint(Tmp1Index); public void EmitSttmp() => EmitStint(Tmp1Index); public void EmitLdvectmp() => EmitLdvec(Tmp5Index); public void EmitStvectmp() => EmitStvec(Tmp5Index); public void EmitLdint(int Index) => Ldloc(Index, AIoType.Int); public void EmitStint(int Index) => Stloc(Index, AIoType.Int); public void EmitLdvec(int Index) => Ldloc(Index, AIoType.Vector); public void EmitStvec(int Index) => Stloc(Index, AIoType.Vector); public void EmitLdflg(int Index) => Ldloc(Index, AIoType.Flag); public void EmitStflg(int Index) { OptOpLastFlagSet = CurrOp; Stloc(Index, AIoType.Flag); } private void Ldloc(int Index, AIoType IoType) { ILBlock.Add(new AILOpCodeLoad(Index, IoType, CurrOp.RegisterSize)); } private void Ldloc(int Index, AIoType IoType, ARegisterSize RegisterSize) { ILBlock.Add(new AILOpCodeLoad(Index, IoType, RegisterSize)); } private void Stloc(int Index, AIoType IoType) { ILBlock.Add(new AILOpCodeStore(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 AILOpCodeCall(MthdInfo)); } public void EmitLdc_I(long Value) { if (CurrOp.RegisterSize == ARegisterSize.Int32) { EmitLdc_I4((int)Value); } else { EmitLdc_I8(Value); } } public void EmitLdc_I4(int Value) { ILBlock.Add(new AILOpCodeConst(Value)); } public void EmitLdc_I8(long Value) { ILBlock.Add(new AILOpCodeConst(Value)); } public void EmitLdc_R4(float Value) { ILBlock.Add(new AILOpCodeConst(Value)); } public void EmitLdc_R8(double Value) { ILBlock.Add(new AILOpCodeConst(Value)); } public void EmitZNFlagCheck() { EmitZNCheck(OpCodes.Ceq, (int)APState.ZBit); EmitZNCheck(OpCodes.Clt, (int)APState.NBit); } private void EmitZNCheck(OpCode ILCmpOp, int Flag) { Emit(OpCodes.Dup); Emit(OpCodes.Ldc_I4_0); if (CurrOp.RegisterSize != ARegisterSize.Int32) { Emit(OpCodes.Conv_I8); } Emit(ILCmpOp); EmitStflg(Flag); } } }