diff options
| author | emmauss <emmausssss@gmail.com> | 2018-02-20 22:09:23 +0200 |
|---|---|---|
| committer | gdkchan <gab.dark.100@gmail.com> | 2018-02-20 17:09:23 -0300 |
| commit | 62b827f474f0aa2152dd339fcc7cf31084e16a0b (patch) | |
| tree | 0e5c55b341aee4db0ccb841a084f253ec5e05657 /ChocolArm64/Translation | |
| parent | cb665bb715834526d73c9469d16114b287faaecd (diff) | |
Split main project into core,graphics and chocolarm4 subproject (#29)
Diffstat (limited to 'ChocolArm64/Translation')
| -rw-r--r-- | ChocolArm64/Translation/AILBlock.cs | 65 | ||||
| -rw-r--r-- | ChocolArm64/Translation/AILEmitter.cs | 187 | ||||
| -rw-r--r-- | ChocolArm64/Translation/AILEmitterCtx.cs | 487 | ||||
| -rw-r--r-- | ChocolArm64/Translation/AILLabel.cs | 28 | ||||
| -rw-r--r-- | ChocolArm64/Translation/AILOpCode.cs | 19 | ||||
| -rw-r--r-- | ChocolArm64/Translation/AILOpCodeBranch.cs | 21 | ||||
| -rw-r--r-- | ChocolArm64/Translation/AILOpCodeCall.cs | 20 | ||||
| -rw-r--r-- | ChocolArm64/Translation/AILOpCodeConst.cs | 81 | ||||
| -rw-r--r-- | ChocolArm64/Translation/AILOpCodeLoad.cs | 77 | ||||
| -rw-r--r-- | ChocolArm64/Translation/AILOpCodeLog.cs | 17 | ||||
| -rw-r--r-- | ChocolArm64/Translation/AILOpCodeStore.cs | 75 | ||||
| -rw-r--r-- | ChocolArm64/Translation/AIoType.cs | 15 | ||||
| -rw-r--r-- | ChocolArm64/Translation/ALocalAlloc.cs | 231 | ||||
| -rw-r--r-- | ChocolArm64/Translation/IAILEmit.cs | 7 | ||||
| -rw-r--r-- | ChocolArm64/Translation/ILGeneratorEx.cs | 129 |
15 files changed, 1459 insertions, 0 deletions
diff --git a/ChocolArm64/Translation/AILBlock.cs b/ChocolArm64/Translation/AILBlock.cs new file mode 100644 index 00000000..bed195aa --- /dev/null +++ b/ChocolArm64/Translation/AILBlock.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; + +namespace ChocolArm64.Translation +{ + class AILBlock : IAILEmit + { + public long IntInputs { get; private set; } + public long IntOutputs { get; private set; } + + public long VecInputs { get; private set; } + public long VecOutputs { get; private set; } + + public bool HasStateStore { get; private set; } + + public List<IAILEmit> ILEmitters { get; private set; } + + public AILBlock Next { get; set; } + public AILBlock Branch { get; set; } + + public AILBlock() + { + ILEmitters = new List<IAILEmit>(); + } + + public void Add(IAILEmit ILEmitter) + { + if (ILEmitter is AILOpCodeLoad Ld && AILEmitter.IsRegIndex(Ld.Index)) + { + switch (Ld.IoType) + { + case AIoType.Flag: IntInputs |= ((1L << Ld.Index) << 32) & ~IntOutputs; break; + case AIoType.Int: IntInputs |= (1L << Ld.Index) & ~IntOutputs; break; + case AIoType.Vector: VecInputs |= (1L << Ld.Index) & ~VecOutputs; break; + } + } + else if (ILEmitter is AILOpCodeStore St) + { + if (AILEmitter.IsRegIndex(St.Index)) + { + switch (St.IoType) + { + case AIoType.Flag: IntOutputs |= (1L << St.Index) << 32; break; + case AIoType.Int: IntOutputs |= 1L << St.Index; break; + case AIoType.Vector: VecOutputs |= 1L << St.Index; break; + } + } + + if (St.IoType == AIoType.Fields) + { + HasStateStore = true; + } + } + + ILEmitters.Add(ILEmitter); + } + + public void Emit(AILEmitter Context) + { + foreach (IAILEmit ILEmitter in ILEmitters) + { + ILEmitter.Emit(Context); + } + } + } +}
\ No newline at end of file diff --git a/ChocolArm64/Translation/AILEmitter.cs b/ChocolArm64/Translation/AILEmitter.cs new file mode 100644 index 00000000..8f6e1210 --- /dev/null +++ b/ChocolArm64/Translation/AILEmitter.cs @@ -0,0 +1,187 @@ +using ChocolArm64.Decoder; +using ChocolArm64.State; +using System; +using System.Collections.Generic; +using System.Reflection.Emit; + +namespace ChocolArm64.Translation +{ + class AILEmitter + { + public ALocalAlloc LocalAlloc { get; private set; } + + public ILGenerator Generator { get; private set; } + + private Dictionary<ARegister, int> Locals; + + private AILBlock[] ILBlocks; + + private AILBlock Root; + + private ATranslatedSub Subroutine; + + private string SubName; + + private int LocalsCount; + + public AILEmitter(ABlock[] Graph, ABlock Root, string SubName) + { + this.SubName = SubName; + + Locals = new Dictionary<ARegister, int>(); + + ILBlocks = new AILBlock[Graph.Length]; + + AILBlock GetBlock(int Index) + { + if (Index < 0 || Index >= ILBlocks.Length) + { + return null; + } + + if (ILBlocks[Index] == null) + { + ILBlocks[Index] = new AILBlock(); + } + + return ILBlocks[Index]; + } + + for (int Index = 0; Index < ILBlocks.Length; Index++) + { + AILBlock Block = GetBlock(Index); + + Block.Next = GetBlock(Array.IndexOf(Graph, Graph[Index].Next)); + Block.Branch = GetBlock(Array.IndexOf(Graph, Graph[Index].Branch)); + } + + this.Root = ILBlocks[Array.IndexOf(Graph, Root)]; + } + + public ATranslatedSub GetSubroutine() + { + LocalAlloc = new ALocalAlloc(ILBlocks, Root); + + InitSubroutine(); + InitLocals(); + + foreach (AILBlock ILBlock in ILBlocks) + { + ILBlock.Emit(this); + } + + return Subroutine; + } + + public AILBlock GetILBlock(int Index) => ILBlocks[Index]; + + private void InitLocals() + { + int ParamsStart = ATranslatedSub.FixedArgTypes.Length; + + Locals = new Dictionary<ARegister, int>(); + + for (int Index = 0; Index < Subroutine.Params.Count; Index++) + { + ARegister Reg = Subroutine.Params[Index]; + + Generator.EmitLdarg(Index + ParamsStart); + Generator.EmitStloc(GetLocalIndex(Reg)); + } + } + + private void InitSubroutine() + { + List<ARegister> Params = new List<ARegister>(); + + void SetParams(long Inputs, ARegisterType BaseType) + { + for (int Bit = 0; Bit < 64; Bit++) + { + long Mask = 1L << Bit; + + if ((Inputs & Mask) != 0) + { + Params.Add(GetRegFromBit(Bit, BaseType)); + } + } + } + + SetParams(LocalAlloc.GetIntInputs(Root), ARegisterType.Int); + SetParams(LocalAlloc.GetVecInputs(Root), ARegisterType.Vector); + + DynamicMethod Mthd = new DynamicMethod(SubName, typeof(long), GetParamTypes(Params)); + + Generator = Mthd.GetILGenerator(); + + Subroutine = new ATranslatedSub(Mthd, Params); + } + + private Type[] GetParamTypes(IList<ARegister> Params) + { + Type[] FixedArgs = ATranslatedSub.FixedArgTypes; + + Type[] Output = new Type[Params.Count + FixedArgs.Length]; + + FixedArgs.CopyTo(Output, 0); + + int TypeIdx = FixedArgs.Length; + + for (int Index = 0; Index < Params.Count; Index++) + { + Output[TypeIdx++] = GetFieldType(Params[Index].Type); + } + + return Output; + } + + public int GetLocalIndex(ARegister Reg) + { + if (!Locals.TryGetValue(Reg, out int Index)) + { + Generator.DeclareLocal(GetLocalType(Reg)); + + Index = LocalsCount++; + + Locals.Add(Reg, Index); + } + + return Index; + } + + public Type GetLocalType(ARegister Reg) => GetFieldType(Reg.Type); + + public Type GetFieldType(ARegisterType RegType) + { + switch (RegType) + { + case ARegisterType.Flag: return typeof(bool); + case ARegisterType.Int: return typeof(ulong); + case ARegisterType.Vector: return typeof(AVec); + } + + throw new ArgumentException(nameof(RegType)); + } + + public static ARegister GetRegFromBit(int Bit, ARegisterType BaseType) + { + if (Bit < 32) + { + return new ARegister(Bit, BaseType); + } + else if (BaseType == ARegisterType.Int) + { + return new ARegister(Bit & 0x1f, ARegisterType.Flag); + } + else + { + throw new ArgumentOutOfRangeException(nameof(Bit)); + } + } + + public static bool IsRegIndex(int Index) + { + return Index >= 0 && Index < 32; + } + } +}
\ No newline at end of file diff --git a/ChocolArm64/Translation/AILEmitterCtx.cs b/ChocolArm64/Translation/AILEmitterCtx.cs new file mode 100644 index 00000000..cf644540 --- /dev/null +++ b/ChocolArm64/Translation/AILEmitterCtx.cs @@ -0,0 +1,487 @@ +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<long, AILLabel> Labels; + + private AILEmitter Emitter; + + private AILBlock ILBlock; + + private AOpCode LastCmpOp; + private AOpCode LastFlagOp; + + private int BlkIndex; + private int OpcIndex; + + private ABlock[] Graph; + private ABlock Root; + public ABlock CurrBlock => Graph[BlkIndex]; + public AOpCode CurrOp => Graph[BlkIndex].OpCodes[OpcIndex]; + + //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) + { + this.Translator = Translator; + this.Graph = Graph; + this.Root = Root; + + string SubName = $"Sub{Root.Position:X16}"; + + Labels = new Dictionary<long, AILLabel>(); + + Emitter = new AILEmitter(Graph, Root, SubName); + + ILBlock = Emitter.GetILBlock(0); + + OpcIndex = -1; + + if (!AdvanceOpCode()) + { + throw new ArgumentException(nameof(Graph)); + } + } + + public ATranslatedSub GetSubroutine() => Emitter.GetSubroutine(); + + public bool AdvanceOpCode() + { + while (++OpcIndex >= (CurrBlock?.OpCodes.Count ?? 0)) + { + if (BlkIndex + 1 >= Graph.Length) + { + return false; + } + + BlkIndex++; + OpcIndex = -1; + + ILBlock = Emitter.GetILBlock(BlkIndex); + } + + return true; + } + + public void EmitOpCode() + { + if (OpcIndex == 0) + { + MarkLabel(GetLabel(CurrBlock.Position)); + } + + CurrOp.Emitter(this); + } + + public bool TryOptEmitSubroutineCall() + { + 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); + + return true; + } + + public void TryOptMarkCondWithoutCmp() + { + LastCmpOp = CurrOp; + + AInstEmitAluHelper.EmitDataLoadOpers(this); + + Stloc(Tmp4Index, AIoType.Int); + Stloc(Tmp3Index, AIoType.Int); + } + + private Dictionary<ACond, OpCode> BranchOps = new Dictionary<ACond, OpCode>() + { + { 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 (LastCmpOp != null && LastFlagOp == LastCmpOp && BranchOps.ContainsKey(Cond)) + { + Ldloc(Tmp3Index, AIoType.Int, LastCmpOp.RegisterSize); + Ldloc(Tmp4Index, AIoType.Int, LastCmpOp.RegisterSize); + + if (LastCmpOp.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; + } + + if (IntType == AIntType.UInt64 || + IntType == AIntType.Int64) + { + return; + } + + if (CurrOp.RegisterSize != ARegisterSize.Int32) + { + Emit(IntType >= AIntType.Int8 + ? OpCodes.Conv_I8 + : OpCodes.Conv_U8); + } + } + + 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) + { + LastFlagOp = 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 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); + } + } +}
\ No newline at end of file diff --git a/ChocolArm64/Translation/AILLabel.cs b/ChocolArm64/Translation/AILLabel.cs new file mode 100644 index 00000000..0ee39ad7 --- /dev/null +++ b/ChocolArm64/Translation/AILLabel.cs @@ -0,0 +1,28 @@ +using System.Reflection.Emit; + +namespace ChocolArm64.Translation +{ + class AILLabel : IAILEmit + { + private bool HasLabel; + + private Label Lbl; + + public void Emit(AILEmitter Context) + { + Context.Generator.MarkLabel(GetLabel(Context)); + } + + public Label GetLabel(AILEmitter Context) + { + if (!HasLabel) + { + Lbl = Context.Generator.DefineLabel(); + + HasLabel = true; + } + + return Lbl; + } + } +}
\ No newline at end of file diff --git a/ChocolArm64/Translation/AILOpCode.cs b/ChocolArm64/Translation/AILOpCode.cs new file mode 100644 index 00000000..a4bc93a0 --- /dev/null +++ b/ChocolArm64/Translation/AILOpCode.cs @@ -0,0 +1,19 @@ +using System.Reflection.Emit; + +namespace ChocolArm64.Translation +{ + struct AILOpCode : IAILEmit + { + private OpCode ILOp; + + public AILOpCode(OpCode ILOp) + { + this.ILOp = ILOp; + } + + public void Emit(AILEmitter Context) + { + Context.Generator.Emit(ILOp); + } + } +}
\ No newline at end of file diff --git a/ChocolArm64/Translation/AILOpCodeBranch.cs b/ChocolArm64/Translation/AILOpCodeBranch.cs new file mode 100644 index 00000000..e4caad1f --- /dev/null +++ b/ChocolArm64/Translation/AILOpCodeBranch.cs @@ -0,0 +1,21 @@ +using System.Reflection.Emit; + +namespace ChocolArm64.Translation +{ + struct AILOpCodeBranch : IAILEmit + { + private OpCode ILOp; + private AILLabel Label; + + public AILOpCodeBranch(OpCode ILOp, AILLabel Label) + { + this.ILOp = ILOp; + this.Label = Label; + } + + public void Emit(AILEmitter Context) + { + Context.Generator.Emit(ILOp, Label.GetLabel(Context)); + } + } +}
\ No newline at end of file diff --git a/ChocolArm64/Translation/AILOpCodeCall.cs b/ChocolArm64/Translation/AILOpCodeCall.cs new file mode 100644 index 00000000..8cd944eb --- /dev/null +++ b/ChocolArm64/Translation/AILOpCodeCall.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Reflection.Emit; + +namespace ChocolArm64.Translation +{ + struct AILOpCodeCall : IAILEmit + { + private MethodInfo MthdInfo; + + public AILOpCodeCall(MethodInfo MthdInfo) + { + this.MthdInfo = MthdInfo; + } + + public void Emit(AILEmitter Context) + { + Context.Generator.Emit(OpCodes.Call, MthdInfo); + } + } +}
\ No newline at end of file diff --git a/ChocolArm64/Translation/AILOpCodeConst.cs b/ChocolArm64/Translation/AILOpCodeConst.cs new file mode 100644 index 00000000..80150ec5 --- /dev/null +++ b/ChocolArm64/Translation/AILOpCodeConst.cs @@ -0,0 +1,81 @@ +using System.Reflection.Emit; +using System.Runtime.InteropServices; + +namespace ChocolArm64.Translation +{ + class AILOpCodeConst : IAILEmit + { + [StructLayout(LayoutKind.Explicit, Size = 8)] + private struct ImmVal + { + [FieldOffset(0)] public int I4; + [FieldOffset(0)] public long I8; + [FieldOffset(0)] public float R4; + [FieldOffset(0)] public double R8; + } + + private ImmVal Value; + + private enum ConstType + { + Int32, + Int64, + Single, + Double + } + + private ConstType Type; + + private AILOpCodeConst(ConstType Type) + { + this.Type = Type; + } + + public AILOpCodeConst(int Value) : this(ConstType.Int32) + { + this.Value = new ImmVal { I4 = Value }; + } + + public AILOpCodeConst(long Value) : this(ConstType.Int64) + { + this.Value = new ImmVal { I8 = Value }; + } + + public AILOpCodeConst(float Value) : this(ConstType.Single) + { + this.Value = new ImmVal { R4 = Value }; + } + + public AILOpCodeConst(double Value) : this(ConstType.Double) + { + this.Value = new ImmVal { R8 = Value }; + } + + public void Emit(AILEmitter Context) + { + switch (Type) + { + case ConstType.Int32: Context.Generator.EmitLdc_I4(Value.I4); break; + + case ConstType.Int64: + { + if (Value.I8 >= int.MinValue && + Value.I8 <= int.MaxValue) + { + Context.Generator.EmitLdc_I4(Value.I4); + + Context.Generator.Emit(OpCodes.Conv_I8); + } + else + { + Context.Generator.Emit(OpCodes.Ldc_I8, Value.I8); + } + break; + } + + case ConstType.Single: Context.Generator.Emit(OpCodes.Ldc_R4, Value.R4); break; + case ConstType.Double: Context.Generator.Emit(OpCodes.Ldc_R8, Value.R8); break; + } + } + } +}
\ No newline at end of file diff --git a/ChocolArm64/Translation/AILOpCodeLoad.cs b/ChocolArm64/Translation/AILOpCodeLoad.cs new file mode 100644 index 00000000..7cb431e2 --- /dev/null +++ b/ChocolArm64/Translation/AILOpCodeLoad.cs @@ -0,0 +1,77 @@ +using ChocolArm64.State; +using System.Reflection.Emit; + +namespace ChocolArm64.Translation +{ + struct AILOpCodeLoad : IAILEmit + { + public int Index { get; private set; } + + public AIoType IoType { get; private set; } + + public ARegisterSize RegisterSize { get; private set; } + + public AILOpCodeLoad(int Index, AIoType IoType) : this(Index, IoType, ARegisterSize.Int64) { } + + public AILOpCodeLoad(int Index, AIoType IoType, ARegisterSize RegisterSize) + { + this.IoType = IoType; + this.Index = Index; + this.RegisterSize = RegisterSize; + } + + public void Emit(AILEmitter Context) + { + switch (IoType) + { + case AIoType.Arg: Context.Generator.EmitLdarg(Index); break; + + case AIoType.Fields: + { + long IntInputs = Context.LocalAlloc.GetIntInputs(Context.GetILBlock(Index)); + long VecInputs = Context.LocalAlloc.GetVecInputs(Context.GetILBlock(Index)); + + LoadLocals(Context, IntInputs, ARegisterType.Int); + LoadLocals(Context, VecInputs, ARegisterType.Vector); + + break; + } + + case AIoType.Flag: EmitLdloc(Context, Index, ARegisterType.Flag); break; + case AIoType.Int: EmitLdloc(Context, Index, ARegisterType.Int); break; + case AIoType.Vector: EmitLdloc(Context, Index, ARegisterType.Vector); break; + } + } + + private void LoadLocals(AILEmitter Context, long Inputs, ARegisterType BaseType) + { + for (int Bit = 0; Bit < 64; Bit++) + { + long Mask = 1L << Bit; + + if ((Inputs & Mask) != 0) + { + ARegister Reg = AILEmitter.GetRegFromBit(Bit, BaseType); + + Context.Generator.EmitLdarg(ATranslatedSub.StateArgIdx); + Context.Generator.Emit(OpCodes.Ldfld, Reg.GetField()); + + Context.Generator.EmitStloc(Context.GetLocalIndex(Reg)); + } + } + } + + private void EmitLdloc(AILEmitter Context, int Index, ARegisterType RegisterType) + { + ARegister Reg = new ARegister(Index, RegisterType); + + Context.Generator.EmitLdloc(Context.GetLocalIndex(Reg)); + + if (RegisterType == ARegisterType.Int && + RegisterSize == ARegisterSize.Int32) + { + Context.Generator.Emit(OpCodes.Conv_U4); + } + } + } +}
\ No newline at end of file diff --git a/ChocolArm64/Translation/AILOpCodeLog.cs b/ChocolArm64/Translation/AILOpCodeLog.cs new file mode 100644 index 00000000..1338ca1f --- /dev/null +++ b/ChocolArm64/Translation/AILOpCodeLog.cs @@ -0,0 +1,17 @@ +namespace ChocolArm64.Translation +{ + struct AILOpCodeLog : IAILEmit + { + private string Text; + + public AILOpCodeLog(string Text) + { + this.Text = Text; + } + + public void Emit(AILEmitter Context) + { + Context.Generator.EmitWriteLine(Text); + } + } +}
\ No newline at end of file diff --git a/ChocolArm64/Translation/AILOpCodeStore.cs b/ChocolArm64/Translation/AILOpCodeStore.cs new file mode 100644 index 00000000..c4ea53ab --- /dev/null +++ b/ChocolArm64/Translation/AILOpCodeStore.cs @@ -0,0 +1,75 @@ +using ChocolArm64.State; +using System.Reflection.Emit; + +namespace ChocolArm64.Translation +{ + struct AILOpCodeStore : IAILEmit + { + public int Index { get; private set; } + + public AIoType IoType { get; private set; } + + public ARegisterSize RegisterSize { get; private set; } + + public AILOpCodeStore(int Index, AIoType IoType, ARegisterSize RegisterSize = ARegisterSize.Int64) + { + this.IoType = IoType; + this.Index = Index; + this.RegisterSize = RegisterSize; + } + + public void Emit(AILEmitter Context) + { + switch (IoType) + { + case AIoType.Arg: Context.Generator.EmitStarg(Index); break; + + case AIoType.Fields: + { + long IntOutputs = Context.LocalAlloc.GetIntOutputs(Context.GetILBlock(Index)); + long VecOutputs = Context.LocalAlloc.GetVecOutputs(Context.GetILBlock(Index)); + + StoreLocals(Context, IntOutputs, ARegisterType.Int); + StoreLocals(Context, VecOutputs, ARegisterType.Vector); + + break; + } + + case AIoType.Flag: EmitStloc(Context, Index, ARegisterType.Flag); break; + case AIoType.Int: EmitStloc(Context, Index, ARegisterType.Int); break; + case AIoType.Vector: EmitStloc(Context, Index, ARegisterType.Vector); break; + } + } + + private void StoreLocals(AILEmitter Context, long Outputs, ARegisterType BaseType) + { + for (int Bit = 0; Bit < 64; Bit++) + { + long Mask = 1L << Bit; + + if ((Outputs & Mask) != 0) + { + ARegister Reg = AILEmitter.GetRegFromBit(Bit, BaseType); + + Context.Generator.EmitLdarg(ATranslatedSub.StateArgIdx); + Context.Generator.EmitLdloc(Context.GetLocalIndex(Reg)); + + Context.Generator.Emit(OpCodes.Stfld, Reg.GetField()); + } + } + } + + private void EmitStloc(AILEmitter Context, int Index, ARegisterType RegisterType) + { + ARegister Reg = new ARegister(Index, RegisterType); + + if (RegisterType == ARegisterType.Int && + RegisterSize == ARegisterSize.Int32) + { + Context.Generator.Emit(OpCodes.Conv_U8); + } + + Context.Generator.EmitStloc(Context.GetLocalIndex(Reg)); + } + } +}
\ No newline at end of file diff --git a/ChocolArm64/Translation/AIoType.cs b/ChocolArm64/Translation/AIoType.cs new file mode 100644 index 00000000..94f89081 --- /dev/null +++ b/ChocolArm64/Translation/AIoType.cs @@ -0,0 +1,15 @@ +using System; + +namespace ChocolArm64.Translation +{ + [Flags] + enum AIoType + { + Arg, + Fields, + Flag, + Int, + Float, + Vector + } +}
\ No newline at end of file diff --git a/ChocolArm64/Translation/ALocalAlloc.cs b/ChocolArm64/Translation/ALocalAlloc.cs new file mode 100644 index 00000000..0661ddc8 --- /dev/null +++ b/ChocolArm64/Translation/ALocalAlloc.cs @@ -0,0 +1,231 @@ +using System.Collections.Generic; + +namespace ChocolArm64.Translation +{ + class ALocalAlloc + { + private class PathIo + { + private Dictionary<AILBlock, long> AllInputs; + private Dictionary<AILBlock, long> CmnOutputs; + + private long AllOutputs; + + public PathIo() + { + AllInputs = new Dictionary<AILBlock, long>(); + CmnOutputs = new Dictionary<AILBlock, long>(); + } + + public PathIo(AILBlock Root, long Inputs, long Outputs) : this() + { + Set(Root, Inputs, Outputs); + } + + public void Set(AILBlock Root, long Inputs, long Outputs) + { + if (!AllInputs.TryAdd(Root, Inputs)) + { + AllInputs[Root] |= Inputs; + } + + if (!CmnOutputs.TryAdd(Root, Outputs)) + { + CmnOutputs[Root] &= Outputs; + } + + AllOutputs |= Outputs; + } + + public long GetInputs(AILBlock Root) + { + if (AllInputs.TryGetValue(Root, out long Inputs)) + { + return Inputs | (AllOutputs & ~CmnOutputs[Root]); + } + + return 0; + } + + public long GetOutputs() + { + return AllOutputs; + } + } + + private Dictionary<AILBlock, PathIo> IntPaths; + private Dictionary<AILBlock, PathIo> VecPaths; + + private struct BlockIo + { + public AILBlock Block; + public AILBlock Entry; + + public long IntInputs; + public long VecInputs; + public long IntOutputs; + public long VecOutputs; + } + + private const int MaxOptGraphLength = 120; + + public ALocalAlloc(AILBlock[] Graph, AILBlock Root) + { + IntPaths = new Dictionary<AILBlock, PathIo>(); + VecPaths = new Dictionary<AILBlock, PathIo>(); + + if (Graph.Length < MaxOptGraphLength) + { + InitializeOptimal(Graph, Root); + } + else + { + InitializeFast(Graph); + } + } + + private void InitializeOptimal(AILBlock[] Graph, AILBlock Root) + { + //This will go through all possible paths on the graph, + //and store all inputs/outputs for each block. A register + //that was previously written to already is not considered an input. + //When a block can be reached by more than one path, then the + //output from all paths needs to be set for this block, and + //only outputs present in all of the parent blocks can be considered + //when doing input elimination. Each block chain have a root, that's where + //the code starts executing. They are present on the subroutine start point, + //and on call return points too (address written to X30 by BL). + HashSet<BlockIo> Visited = new HashSet<BlockIo>(); + + Queue<BlockIo> Unvisited = new Queue<BlockIo>(); + + void Enqueue(BlockIo Block) + { + if (!Visited.Contains(Block)) + { + Unvisited.Enqueue(Block); + + Visited.Add(Block); + } + } + + Enqueue(new BlockIo() + { + Block = Root, + Entry = Root + }); + + while (Unvisited.Count > 0) + { + BlockIo Current = Unvisited.Dequeue(); + + Current.IntInputs |= Current.Block.IntInputs & ~Current.IntOutputs; + Current.VecInputs |= Current.Block.VecInputs & ~Current.VecOutputs; + Current.IntOutputs |= Current.Block.IntOutputs; + Current.VecOutputs |= Current.Block.VecOutputs; + + //Check if this is a exit block + //(a block that returns or calls another sub). + if ((Current.Block.Next == null && + Current.Block.Branch == null) || Current.Block.HasStateStore) + { + if (!IntPaths.TryGetValue(Current.Block, out PathIo IntPath)) + { + IntPaths.Add(Current.Block, IntPath = new PathIo()); + } + + if (!VecPaths.TryGetValue(Current.Block, out PathIo VecPath)) + { + VecPaths.Add(Current.Block, VecPath = new PathIo()); + } + + IntPath.Set(Current.Entry, Current.IntInputs, Current.IntOutputs); + VecPath.Set(Current.Entry, Current.VecInputs, Current.VecOutputs); + } + + void EnqueueFromCurrent(AILBlock Block, bool RetTarget) + { + BlockIo BlkIO = new BlockIo() { Block = Block }; + + if (RetTarget) + { + BlkIO.Entry = Block; + BlkIO.IntInputs = 0; + BlkIO.VecInputs = 0; + BlkIO.IntOutputs = 0; + BlkIO.VecOutputs = 0; + } + else + { + BlkIO.Entry = Current.Entry; + BlkIO.IntInputs = Current.IntInputs; + BlkIO.VecInputs = Current.VecInputs; + BlkIO.IntOutputs = Current.IntOutputs; + BlkIO.VecOutputs = Current.VecOutputs; + } + + Enqueue(BlkIO); + } + + if (Current.Block.Next != null) + { + EnqueueFromCurrent(Current.Block.Next, Current.Block.HasStateStore); + } + + if (Current.Block.Branch != null) + { + EnqueueFromCurrent(Current.Block.Branch, false); + } + } + } + + private void InitializeFast(AILBlock[] Graph) + { + //This is WAY faster than InitializeOptimal, but results in + //uneeded loads and stores, so the resulting code will be slower. + long IntInputs = 0; + long IntOutputs = 0; + long VecInputs = 0; + long VecOutputs = 0; + + foreach (AILBlock Block in Graph) + { + IntInputs |= Block.IntInputs; + IntOutputs |= Block.IntOutputs; + VecInputs |= Block.VecInputs; + VecOutputs |= Block.VecOutputs; + } + + //It's possible that not all code paths writes to those output registers, + //in those cases if we attempt to write an output registers that was + //not written, we will be just writing zero and messing up the old register value. + //So we just need to ensure that all outputs are loaded. + IntInputs |= IntOutputs; + VecInputs |= VecOutputs; + + foreach (AILBlock Block in Graph) + { + IntPaths.Add(Block, new PathIo(Block, IntInputs, IntOutputs)); + VecPaths.Add(Block, new PathIo(Block, VecInputs, VecOutputs)); + } + } + + public long GetIntInputs(AILBlock Root) => GetInputsImpl(Root, IntPaths.Values); + public long GetVecInputs(AILBlock Root) => GetInputsImpl(Root, VecPaths.Values); + + private long GetInputsImpl(AILBlock Root, IEnumerable<PathIo> Values) + { + long Inputs = 0; + + foreach (PathIo Path in Values) + { + Inputs |= Path.GetInputs(Root); + } + + return Inputs; + } + + public long GetIntOutputs(AILBlock Block) => IntPaths[Block].GetOutputs(); + public long GetVecOutputs(AILBlock Block) => VecPaths[Block].GetOutputs(); + } +}
\ No newline at end of file diff --git a/ChocolArm64/Translation/IAILEmit.cs b/ChocolArm64/Translation/IAILEmit.cs new file mode 100644 index 00000000..6e4e9a78 --- /dev/null +++ b/ChocolArm64/Translation/IAILEmit.cs @@ -0,0 +1,7 @@ +namespace ChocolArm64.Translation +{ + interface IAILEmit + { + void Emit(AILEmitter Context); + } +}
\ No newline at end of file diff --git a/ChocolArm64/Translation/ILGeneratorEx.cs b/ChocolArm64/Translation/ILGeneratorEx.cs new file mode 100644 index 00000000..abb35ec3 --- /dev/null +++ b/ChocolArm64/Translation/ILGeneratorEx.cs @@ -0,0 +1,129 @@ +using System; + +namespace ChocolArm64 +{ + using System.Reflection.Emit; + + static class ILGeneratorEx + { + public static void EmitLdc_I4(this ILGenerator Generator,int Value) + { + switch (Value) + { + case 0: Generator.Emit(OpCodes.Ldc_I4_0); break; + case 1: Generator.Emit(OpCodes.Ldc_I4_1); break; + case 2: Generator.Emit(OpCodes.Ldc_I4_2); break; + case 3: Generator.Emit(OpCodes.Ldc_I4_3); break; + case 4: Generator.Emit(OpCodes.Ldc_I4_4); break; + case 5: Generator.Emit(OpCodes.Ldc_I4_5); break; + case 6: Generator.Emit(OpCodes.Ldc_I4_6); break; + case 7: Generator.Emit(OpCodes.Ldc_I4_7); break; + case 8: Generator.Emit(OpCodes.Ldc_I4_8); break; + case -1: Generator.Emit(OpCodes.Ldc_I4_M1); break; + default: Generator.Emit(OpCodes.Ldc_I4, Value); break; + } + } + + public static void EmitLdarg(this ILGenerator Generator, int Index) + { + switch (Index) + { + case 0: Generator.Emit(OpCodes.Ldarg_0); break; + case 1: Generator.Emit(OpCodes.Ldarg_1); break; + case 2: Generator.Emit(OpCodes.Ldarg_2); break; + case 3: Generator.Emit(OpCodes.Ldarg_3); break; + + default: + if ((uint)Index <= byte.MaxValue) + { + Generator.Emit(OpCodes.Ldarg_S, (byte)Index); + } + else if ((uint)Index < ushort.MaxValue) + { + Generator.Emit(OpCodes.Ldarg, (short)Index); + } + else + { + throw new ArgumentOutOfRangeException(nameof(Index)); + } + break; + } + } + + public static void EmitStarg(this ILGenerator Generator, int Index) + { + if ((uint)Index <= byte.MaxValue) + { + Generator.Emit(OpCodes.Starg_S, (byte)Index); + } + else if ((uint)Index < ushort.MaxValue) + { + Generator.Emit(OpCodes.Starg, (short)Index); + } + else + { + throw new ArgumentOutOfRangeException(nameof(Index)); + } + } + + public static void EmitLdloc(this ILGenerator Generator, int Index) + { + switch (Index) + { + case 0: Generator.Emit(OpCodes.Ldloc_0); break; + case 1: Generator.Emit(OpCodes.Ldloc_1); break; + case 2: Generator.Emit(OpCodes.Ldloc_2); break; + case 3: Generator.Emit(OpCodes.Ldloc_3); break; + + default: + if ((uint)Index <= byte.MaxValue) + { + Generator.Emit(OpCodes.Ldloc_S, (byte)Index); + } + else if ((uint)Index < ushort.MaxValue) + { + Generator.Emit(OpCodes.Ldloc, (short)Index); + } + else + { + throw new ArgumentOutOfRangeException(nameof(Index)); + } + break; + } + } + + public static void EmitStloc(this ILGenerator Generator, int Index) + { + switch (Index) + { + case 0: Generator.Emit(OpCodes.Stloc_0); break; + case 1: Generator.Emit(OpCodes.Stloc_1); break; + case 2: Generator.Emit(OpCodes.Stloc_2); break; + case 3: Generator.Emit(OpCodes.Stloc_3); break; + + default: + if ((uint)Index <= byte.MaxValue) + { + Generator.Emit(OpCodes.Stloc_S, (byte)Index); + } + else if ((uint)Index < ushort.MaxValue) + { + Generator.Emit(OpCodes.Stloc, (short)Index); + } + else + { + throw new ArgumentOutOfRangeException(nameof(Index)); + } + break; + } + } + + public static void EmitLdargSeq(this ILGenerator Generator, int Count) + { + for (int Index = 0; Index < Count; Index++) + { + Generator.EmitLdarg(Index); + } + } + } +}
\ No newline at end of file |
