From 9bb400dfea028e0dccfbc73919c7cc0f81b9743f Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sun, 24 Feb 2019 04:24:35 -0300 Subject: [PATCH] Optimize address translation and write tracking on the MMU (#571) * Implement faster address translation and write tracking on the MMU * Rename MemoryAlloc to MemoryManagement, and other nits * Support multi-level page tables * Fix typo * Reword comment a bit * Support scalar vector loads/stores on the memory fast path, and minor fixes * Add missing cast * Alignment * Fix VirtualFree function signature * Change MemoryProtection enum to uint aswell for consistency --- Events/InvalidAccessEventArgs.cs | 14 - Exceptions/VmmPageFaultException.cs | 13 - Instructions/InstEmitMemory.cs | 11 - Instructions/InstEmitMemory32.cs | 6 - Instructions/InstEmitMemoryEx.cs | 22 +- Instructions/InstEmitMemoryHelper.cs | 419 ++++++++++- Instructions/InstEmitSimdMemory.cs | 3 - Instructions/InstEmitSystem.cs | 1 - Memory/CompareExchange128.cs | 6 +- .../{MemoryAlloc.cs => MemoryManagement.cs} | 20 +- ...ryAllocUnix.cs => MemoryManagementUnix.cs} | 2 +- ...cWindows.cs => MemoryManagementWindows.cs} | 8 +- Memory/MemoryManager.cs | 704 ++++++++++-------- Translation/ILEmitterCtx.cs | 132 ++-- Translation/Translator.cs | 4 +- 15 files changed, 870 insertions(+), 495 deletions(-) delete mode 100644 Events/InvalidAccessEventArgs.cs delete mode 100644 Exceptions/VmmPageFaultException.cs rename Memory/{MemoryAlloc.cs => MemoryManagement.cs} (81%) rename Memory/{MemoryAllocUnix.cs => MemoryManagementUnix.cs} (98%) rename Memory/{MemoryAllocWindows.cs => MemoryManagementWindows.cs} (95%) diff --git a/Events/InvalidAccessEventArgs.cs b/Events/InvalidAccessEventArgs.cs deleted file mode 100644 index 9c34975..0000000 --- a/Events/InvalidAccessEventArgs.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace ChocolArm64.Events -{ - public class MemoryAccessEventArgs : EventArgs - { - public long Position { get; private set; } - - public MemoryAccessEventArgs(long position) - { - Position = position; - } - } -} \ No newline at end of file diff --git a/Exceptions/VmmPageFaultException.cs b/Exceptions/VmmPageFaultException.cs deleted file mode 100644 index f33aafc..0000000 --- a/Exceptions/VmmPageFaultException.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace ChocolArm64.Exceptions -{ - public class VmmPageFaultException : Exception - { - private const string ExMsg = "Tried to access unmapped address 0x{0:x16}!"; - - public VmmPageFaultException() { } - - public VmmPageFaultException(long position) : base(string.Format(ExMsg, position)) { } - } -} \ No newline at end of file diff --git a/Instructions/InstEmitMemory.cs b/Instructions/InstEmitMemory.cs index 96f782d..ea779c8 100644 --- a/Instructions/InstEmitMemory.cs +++ b/Instructions/InstEmitMemory.cs @@ -31,8 +31,6 @@ namespace ChocolArm64.Instructions { OpCodeMem64 op = (OpCodeMem64)context.CurrOp; - context.EmitLdarg(TranslatedSub.MemoryArgIdx); - EmitLoadAddress(context); if (signed && op.Extend64) @@ -69,7 +67,6 @@ namespace ChocolArm64.Instructions return; } - context.EmitLdarg(TranslatedSub.MemoryArgIdx); context.EmitLdc_I8(op.Imm); if (op.Signed) @@ -116,13 +113,10 @@ namespace ChocolArm64.Instructions } } - context.EmitLdarg(TranslatedSub.MemoryArgIdx); - EmitLoadAddress(context); EmitReadAndStore(op.Rt); - context.EmitLdarg(TranslatedSub.MemoryArgIdx); context.EmitLdtmp(); context.EmitLdc_I8(1 << op.Size); @@ -137,8 +131,6 @@ namespace ChocolArm64.Instructions { OpCodeMem64 op = (OpCodeMem64)context.CurrOp; - context.EmitLdarg(TranslatedSub.MemoryArgIdx); - EmitLoadAddress(context); if (op is IOpCodeSimd64) @@ -159,8 +151,6 @@ namespace ChocolArm64.Instructions { OpCodeMemPair64 op = (OpCodeMemPair64)context.CurrOp; - context.EmitLdarg(TranslatedSub.MemoryArgIdx); - EmitLoadAddress(context); if (op is IOpCodeSimd64) @@ -174,7 +164,6 @@ namespace ChocolArm64.Instructions EmitWriteCall(context, op.Size); - context.EmitLdarg(TranslatedSub.MemoryArgIdx); context.EmitLdtmp(); context.EmitLdc_I8(1 << op.Size); diff --git a/Instructions/InstEmitMemory32.cs b/Instructions/InstEmitMemory32.cs index 4d6a57a..1e1419e 100644 --- a/Instructions/InstEmitMemory32.cs +++ b/Instructions/InstEmitMemory32.cs @@ -64,9 +64,7 @@ namespace ChocolArm64.Instructions { if ((mask & 1) != 0) { - context.EmitLdarg(TranslatedSub.MemoryArgIdx); context.EmitLdtmp(); - context.EmitLdc_I4(offset); context.Emit(OpCodes.Add); @@ -129,9 +127,7 @@ namespace ChocolArm64.Instructions { if ((mask & 1) != 0) { - context.EmitLdarg(TranslatedSub.MemoryArgIdx); context.EmitLdtmp(); - context.EmitLdc_I4(offset); context.Emit(OpCodes.Add); @@ -198,8 +194,6 @@ namespace ChocolArm64.Instructions context.EmitSttmp(); } - context.EmitLdarg(TranslatedSub.MemoryArgIdx); - if (op.Index) { context.EmitLdtmp(); diff --git a/Instructions/InstEmitMemoryEx.cs b/Instructions/InstEmitMemoryEx.cs index 215fcff..920c695 100644 --- a/Instructions/InstEmitMemoryEx.cs +++ b/Instructions/InstEmitMemoryEx.cs @@ -72,6 +72,8 @@ namespace ChocolArm64.Instructions void WriteExclusiveValue(string propName) { + context.Emit(OpCodes.Dup); + if (op.Size < 3) { context.Emit(OpCodes.Conv_U8); @@ -82,13 +84,6 @@ namespace ChocolArm64.Instructions context.EmitLdtmp2(); context.EmitCallPrivatePropSet(typeof(CpuThreadState), propName); - - context.EmitLdtmp2(); - - if (op.Size < 3) - { - context.Emit(OpCodes.Conv_U4); - } } if (pair) @@ -99,7 +94,6 @@ namespace ChocolArm64.Instructions //method to read 128-bits atomically. if (op.Size == 2) { - context.EmitLdarg(TranslatedSub.MemoryArgIdx); context.EmitLdtmp(); EmitReadZxCall(context, 3); @@ -164,13 +158,12 @@ namespace ChocolArm64.Instructions } else { - throw new InvalidOperationException($"Invalid store size of {1 << op.Size} bytes."); + throw new InvalidOperationException($"Invalid load size of {1 << op.Size} bytes."); } } else { //8, 16, 32 or 64-bits (non-pairwise) load. - context.EmitLdarg(TranslatedSub.MemoryArgIdx); context.EmitLdtmp(); EmitReadZxCall(context, op.Size); @@ -320,9 +313,8 @@ namespace ChocolArm64.Instructions } else { - void EmitWrite(int rt, long offset) + void EmitWriteCall(int rt, long offset) { - context.EmitLdarg(TranslatedSub.MemoryArgIdx); context.EmitLdint(op.Rn); if (offset != 0) @@ -334,14 +326,14 @@ namespace ChocolArm64.Instructions context.EmitLdintzr(rt); - EmitWriteCall(context, op.Size); + InstEmitMemoryHelper.EmitWriteCall(context, op.Size); } - EmitWrite(op.Rt, 0); + EmitWriteCall(op.Rt, 0); if (pair) { - EmitWrite(op.Rt2, 1 << op.Size); + EmitWriteCall(op.Rt2, 1 << op.Size); } } } diff --git a/Instructions/InstEmitMemoryHelper.cs b/Instructions/InstEmitMemoryHelper.cs index f953564..7645e36 100644 --- a/Instructions/InstEmitMemoryHelper.cs +++ b/Instructions/InstEmitMemoryHelper.cs @@ -1,13 +1,20 @@ using ChocolArm64.Decoders; using ChocolArm64.Memory; +using ChocolArm64.State; using ChocolArm64.Translation; using System; using System.Reflection.Emit; +using System.Runtime.Intrinsics.X86; namespace ChocolArm64.Instructions { static class InstEmitMemoryHelper { + private static int _tempIntAddress = ILEmitterCtx.GetIntTempIndex(); + private static int _tempIntValue = ILEmitterCtx.GetIntTempIndex(); + private static int _tempIntPtAddr = ILEmitterCtx.GetIntTempIndex(); + private static int _tempVecValue = ILEmitterCtx.GetVecTempIndex(); + private enum Extension { Zx, @@ -32,9 +39,10 @@ namespace ChocolArm64.Instructions private static void EmitReadCall(ILEmitterCtx context, Extension ext, int size) { - bool isSimd = GetIsSimd(context); + //Save the address into a temp. + context.EmitStint(_tempIntAddress); - string name = null; + bool isSimd = IsSimd(context); if (size < 0 || size > (isSimd ? 4 : 3)) { @@ -43,28 +51,27 @@ namespace ChocolArm64.Instructions if (isSimd) { - switch (size) + if (context.Tier == TranslationTier.Tier0 || !Sse2.IsSupported || size < 2) { - case 0: name = nameof(MemoryManager.ReadVector8); break; - case 1: name = nameof(MemoryManager.ReadVector16); break; - case 2: name = nameof(MemoryManager.ReadVector32); break; - case 3: name = nameof(MemoryManager.ReadVector64); break; - case 4: name = nameof(MemoryManager.ReadVector128); break; + EmitReadVectorFallback(context, size); + } + else + { + EmitReadVector(context, size); } } else { - switch (size) + if (context.Tier == TranslationTier.Tier0) { - case 0: name = nameof(MemoryManager.ReadByte); break; - case 1: name = nameof(MemoryManager.ReadUInt16); break; - case 2: name = nameof(MemoryManager.ReadUInt32); break; - case 3: name = nameof(MemoryManager.ReadUInt64); break; + EmitReadIntFallback(context, size); + } + else + { + EmitReadInt(context, size); } } - context.EmitCall(typeof(MemoryManager), name); - if (!isSimd) { if (ext == Extension.Sx32 || @@ -89,50 +96,390 @@ namespace ChocolArm64.Instructions public static void EmitWriteCall(ILEmitterCtx context, int size) { - bool isSimd = GetIsSimd(context); + bool isSimd = IsSimd(context); - string name = null; + //Save the value into a temp. + if (isSimd) + { + context.EmitStvec(_tempVecValue); + } + else + { + context.EmitStint(_tempIntValue); + } + + //Save the address into a temp. + context.EmitStint(_tempIntAddress); if (size < 0 || size > (isSimd ? 4 : 3)) { throw new ArgumentOutOfRangeException(nameof(size)); } - if (size < 3 && !isSimd) - { - context.Emit(OpCodes.Conv_I4); - } - if (isSimd) { - switch (size) + if (context.Tier == TranslationTier.Tier0 || !Sse2.IsSupported || size < 2) { - case 0: name = nameof(MemoryManager.WriteVector8); break; - case 1: name = nameof(MemoryManager.WriteVector16); break; - case 2: name = nameof(MemoryManager.WriteVector32); break; - case 3: name = nameof(MemoryManager.WriteVector64); break; - case 4: name = nameof(MemoryManager.WriteVector128); break; + EmitWriteVectorFallback(context, size); + } + else + { + EmitWriteVector(context, size); } } else { - switch (size) + if (context.Tier == TranslationTier.Tier0) { - case 0: name = nameof(MemoryManager.WriteByte); break; - case 1: name = nameof(MemoryManager.WriteUInt16); break; - case 2: name = nameof(MemoryManager.WriteUInt32); break; - case 3: name = nameof(MemoryManager.WriteUInt64); break; + EmitWriteIntFallback(context, size); + } + else + { + EmitWriteInt(context, size); } } - - context.EmitCall(typeof(MemoryManager), name); } - private static bool GetIsSimd(ILEmitterCtx context) + private static bool IsSimd(ILEmitterCtx context) { return context.CurrOp is IOpCodeSimd64 && !(context.CurrOp is OpCodeSimdMemMs64 || context.CurrOp is OpCodeSimdMemSs64); } + + private static void EmitReadInt(ILEmitterCtx context, int size) + { + EmitAddressCheck(context, size); + + ILLabel lblFastPath = new ILLabel(); + ILLabel lblSlowPath = new ILLabel(); + ILLabel lblEnd = new ILLabel(); + + context.Emit(OpCodes.Brfalse_S, lblFastPath); + + context.MarkLabel(lblSlowPath); + + EmitReadIntFallback(context, size); + + context.Emit(OpCodes.Br, lblEnd); + + context.MarkLabel(lblFastPath); + + EmitPtPointerLoad(context, lblSlowPath); + + switch (size) + { + case 0: context.Emit(OpCodes.Ldind_U1); break; + case 1: context.Emit(OpCodes.Ldind_U2); break; + case 2: context.Emit(OpCodes.Ldind_U4); break; + case 3: context.Emit(OpCodes.Ldind_I8); break; + } + + context.MarkLabel(lblEnd); + } + + private static void EmitReadVector(ILEmitterCtx context, int size) + { + EmitAddressCheck(context, size); + + ILLabel lblFastPath = new ILLabel(); + ILLabel lblSlowPath = new ILLabel(); + ILLabel lblEnd = new ILLabel(); + + context.Emit(OpCodes.Brfalse_S, lblFastPath); + + context.MarkLabel(lblSlowPath); + + EmitReadVectorFallback(context, size); + + context.Emit(OpCodes.Br, lblEnd); + + context.MarkLabel(lblFastPath); + + EmitPtPointerLoad(context, lblSlowPath); + + switch (size) + { + case 2: context.EmitCall(typeof(Sse), nameof(Sse.LoadScalarVector128)); break; + + case 3: + { + Type[] types = new Type[] { typeof(double*) }; + + context.EmitCall(typeof(Sse2).GetMethod(nameof(Sse2.LoadScalarVector128), types)); + + VectorHelper.EmitCall(context, nameof(VectorHelper.VectorDoubleToSingle)); + + break; + } + + case 4: context.EmitCall(typeof(Sse), nameof(Sse.LoadAlignedVector128)); break; + + throw new InvalidOperationException($"Invalid vector load size of {1 << size} bytes."); + } + + context.MarkLabel(lblEnd); + } + + private static void EmitWriteInt(ILEmitterCtx context, int size) + { + EmitAddressCheck(context, size); + + ILLabel lblFastPath = new ILLabel(); + ILLabel lblSlowPath = new ILLabel(); + ILLabel lblEnd = new ILLabel(); + + context.Emit(OpCodes.Brfalse_S, lblFastPath); + + context.MarkLabel(lblSlowPath); + + EmitWriteIntFallback(context, size); + + context.Emit(OpCodes.Br, lblEnd); + + context.MarkLabel(lblFastPath); + + EmitPtPointerLoad(context, lblSlowPath); + + context.EmitLdint(_tempIntValue); + + if (size < 3) + { + context.Emit(OpCodes.Conv_U4); + } + + switch (size) + { + case 0: context.Emit(OpCodes.Stind_I1); break; + case 1: context.Emit(OpCodes.Stind_I2); break; + case 2: context.Emit(OpCodes.Stind_I4); break; + case 3: context.Emit(OpCodes.Stind_I8); break; + } + + context.MarkLabel(lblEnd); + } + + private static void EmitWriteVector(ILEmitterCtx context, int size) + { + EmitAddressCheck(context, size); + + ILLabel lblFastPath = new ILLabel(); + ILLabel lblSlowPath = new ILLabel(); + ILLabel lblEnd = new ILLabel(); + + context.Emit(OpCodes.Brfalse_S, lblFastPath); + + context.MarkLabel(lblSlowPath); + + EmitWriteVectorFallback(context, size); + + context.Emit(OpCodes.Br, lblEnd); + + context.MarkLabel(lblFastPath); + + EmitPtPointerLoad(context, lblSlowPath); + + context.EmitLdvec(_tempVecValue); + + switch (size) + { + case 2: context.EmitCall(typeof(Sse), nameof(Sse.StoreScalar)); break; + + case 3: + { + VectorHelper.EmitCall(context, nameof(VectorHelper.VectorSingleToDouble)); + + context.EmitCall(typeof(Sse2), nameof(Sse2.StoreScalar)); + + break; + } + + case 4: context.EmitCall(typeof(Sse), nameof(Sse.StoreAligned)); break; + + default: throw new InvalidOperationException($"Invalid vector store size of {1 << size} bytes."); + } + + context.MarkLabel(lblEnd); + } + + private static void EmitAddressCheck(ILEmitterCtx context, int size) + { + long addressCheckMask = ~(context.Memory.AddressSpaceSize - 1); + + addressCheckMask |= (1u << size) - 1; + + context.EmitLdint(_tempIntAddress); + + context.EmitLdc_I(addressCheckMask); + + context.Emit(OpCodes.And); + } + + private static void EmitPtPointerLoad(ILEmitterCtx context, ILLabel lblFallbackPath) + { + context.EmitLdc_I8(context.Memory.PageTable.ToInt64()); + + context.Emit(OpCodes.Conv_I); + + int bit = MemoryManager.PageBits; + + do + { + context.EmitLdint(_tempIntAddress); + + if (context.CurrOp.RegisterSize == RegisterSize.Int32) + { + context.Emit(OpCodes.Conv_U8); + } + + context.EmitLsr(bit); + + bit += context.Memory.PtLevelBits; + + if (bit < context.Memory.AddressSpaceBits) + { + context.EmitLdc_I8(context.Memory.PtLevelMask); + + context.Emit(OpCodes.And); + } + + context.EmitLdc_I8(IntPtr.Size); + + context.Emit(OpCodes.Mul); + context.Emit(OpCodes.Conv_I); + context.Emit(OpCodes.Add); + context.Emit(OpCodes.Ldind_I); + } + while (bit < context.Memory.AddressSpaceBits); + + if (!context.Memory.HasWriteWatchSupport) + { + context.Emit(OpCodes.Conv_U8); + + context.EmitStint(_tempIntPtAddr); + context.EmitLdint(_tempIntPtAddr); + + context.EmitLdc_I8(MemoryManager.PteFlagsMask); + + context.Emit(OpCodes.And); + + context.Emit(OpCodes.Brtrue, lblFallbackPath); + + context.EmitLdint(_tempIntPtAddr); + + context.Emit(OpCodes.Conv_I); + } + + context.EmitLdint(_tempIntAddress); + + context.EmitLdc_I(MemoryManager.PageMask); + + context.Emit(OpCodes.And); + context.Emit(OpCodes.Conv_I); + context.Emit(OpCodes.Add); + } + + private static void EmitReadIntFallback(ILEmitterCtx context, int size) + { + context.EmitLdarg(TranslatedSub.MemoryArgIdx); + context.EmitLdint(_tempIntAddress); + + if (context.CurrOp.RegisterSize == RegisterSize.Int32) + { + context.Emit(OpCodes.Conv_U8); + } + + string fallbackMethodName = null; + + switch (size) + { + case 0: fallbackMethodName = nameof(MemoryManager.ReadByte); break; + case 1: fallbackMethodName = nameof(MemoryManager.ReadUInt16); break; + case 2: fallbackMethodName = nameof(MemoryManager.ReadUInt32); break; + case 3: fallbackMethodName = nameof(MemoryManager.ReadUInt64); break; + } + + context.EmitCall(typeof(MemoryManager), fallbackMethodName); + } + + private static void EmitReadVectorFallback(ILEmitterCtx context, int size) + { + context.EmitLdarg(TranslatedSub.MemoryArgIdx); + context.EmitLdint(_tempIntAddress); + + if (context.CurrOp.RegisterSize == RegisterSize.Int32) + { + context.Emit(OpCodes.Conv_U8); + } + + string fallbackMethodName = null; + + switch (size) + { + case 0: fallbackMethodName = nameof(MemoryManager.ReadVector8); break; + case 1: fallbackMethodName = nameof(MemoryManager.ReadVector16); break; + case 2: fallbackMethodName = nameof(MemoryManager.ReadVector32); break; + case 3: fallbackMethodName = nameof(MemoryManager.ReadVector64); break; + case 4: fallbackMethodName = nameof(MemoryManager.ReadVector128); break; + } + + context.EmitCall(typeof(MemoryManager), fallbackMethodName); + } + + private static void EmitWriteIntFallback(ILEmitterCtx context, int size) + { + context.EmitLdarg(TranslatedSub.MemoryArgIdx); + context.EmitLdint(_tempIntAddress); + + if (context.CurrOp.RegisterSize == RegisterSize.Int32) + { + context.Emit(OpCodes.Conv_U8); + } + + context.EmitLdint(_tempIntValue); + + if (size < 3) + { + context.Emit(OpCodes.Conv_U4); + } + + string fallbackMethodName = null; + + switch (size) + { + case 0: fallbackMethodName = nameof(MemoryManager.WriteByte); break; + case 1: fallbackMethodName = nameof(MemoryManager.WriteUInt16); break; + case 2: fallbackMethodName = nameof(MemoryManager.WriteUInt32); break; + case 3: fallbackMethodName = nameof(MemoryManager.WriteUInt64); break; + } + + context.EmitCall(typeof(MemoryManager), fallbackMethodName); + } + + private static void EmitWriteVectorFallback(ILEmitterCtx context, int size) + { + context.EmitLdarg(TranslatedSub.MemoryArgIdx); + context.EmitLdint(_tempIntAddress); + + if (context.CurrOp.RegisterSize == RegisterSize.Int32) + { + context.Emit(OpCodes.Conv_U8); + } + + context.EmitLdvec(_tempVecValue); + + string fallbackMethodName = null; + + switch (size) + { + case 0: fallbackMethodName = nameof(MemoryManager.WriteVector8); break; + case 1: fallbackMethodName = nameof(MemoryManager.WriteVector16); break; + case 2: fallbackMethodName = nameof(MemoryManager.WriteVector32); break; + case 3: fallbackMethodName = nameof(MemoryManager.WriteVector64); break; + case 4: fallbackMethodName = nameof(MemoryManager.WriteVector128); break; + } + + context.EmitCall(typeof(MemoryManager), fallbackMethodName); + } } } \ No newline at end of file diff --git a/Instructions/InstEmitSimdMemory.cs b/Instructions/InstEmitSimdMemory.cs index 9b84eb8..18ec1d3 100644 --- a/Instructions/InstEmitSimdMemory.cs +++ b/Instructions/InstEmitSimdMemory.cs @@ -45,7 +45,6 @@ namespace ChocolArm64.Instructions if (isLoad) { - context.EmitLdarg(TranslatedSub.MemoryArgIdx); context.EmitLdint(op.Rn); context.EmitLdc_I8(offset); @@ -62,7 +61,6 @@ namespace ChocolArm64.Instructions } else { - context.EmitLdarg(TranslatedSub.MemoryArgIdx); context.EmitLdint(op.Rn); context.EmitLdc_I8(offset); @@ -90,7 +88,6 @@ namespace ChocolArm64.Instructions void EmitMemAddress() { - context.EmitLdarg(TranslatedSub.MemoryArgIdx); context.EmitLdint(op.Rn); context.EmitLdc_I8(offset); diff --git a/Instructions/InstEmitSystem.cs b/Instructions/InstEmitSystem.cs index 0e61d5b..5687768 100644 --- a/Instructions/InstEmitSystem.cs +++ b/Instructions/InstEmitSystem.cs @@ -102,7 +102,6 @@ namespace ChocolArm64.Instructions //DC ZVA for (int offs = 0; offs < (4 << CpuThreadState.DczSizeLog2); offs += 8) { - context.EmitLdarg(TranslatedSub.MemoryArgIdx); context.EmitLdintzr(op.Rt); context.EmitLdc_I(offs); diff --git a/Memory/CompareExchange128.cs b/Memory/CompareExchange128.cs index 0fbe10f..1618ff0 100644 --- a/Memory/CompareExchange128.cs +++ b/Memory/CompareExchange128.cs @@ -95,7 +95,7 @@ namespace ChocolArm64.Memory int cpuId = getCpuId(); - MemoryAlloc.Free(funcPtr); + MemoryManagement.Free(funcPtr); return (cpuId & (1 << 13)) != 0; } @@ -104,7 +104,7 @@ namespace ChocolArm64.Memory { ulong codeLength = (ulong)code.Length; - IntPtr funcPtr = MemoryAlloc.Allocate(codeLength); + IntPtr funcPtr = MemoryManagement.Allocate(codeLength); unsafe { @@ -118,7 +118,7 @@ namespace ChocolArm64.Memory } } - MemoryAlloc.Reprotect(funcPtr, codeLength, MemoryProtection.Execute); + MemoryManagement.Reprotect(funcPtr, codeLength, MemoryProtection.Execute); return funcPtr; } diff --git a/Memory/MemoryAlloc.cs b/Memory/MemoryManagement.cs similarity index 81% rename from Memory/MemoryAlloc.cs rename to Memory/MemoryManagement.cs index a24299c..fa4bc4f 100644 --- a/Memory/MemoryAlloc.cs +++ b/Memory/MemoryManagement.cs @@ -4,7 +4,7 @@ using System.Runtime.InteropServices; namespace ChocolArm64.Memory { - public static class MemoryAlloc + public static class MemoryManagement { public static bool HasWriteWatchSupport => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); @@ -14,12 +14,12 @@ namespace ChocolArm64.Memory { IntPtr sizeNint = new IntPtr((long)size); - return MemoryAllocWindows.Allocate(sizeNint); + return MemoryManagementWindows.Allocate(sizeNint); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - return MemoryAllocUnix.Allocate(size); + return MemoryManagementUnix.Allocate(size); } else { @@ -33,12 +33,12 @@ namespace ChocolArm64.Memory { IntPtr sizeNint = new IntPtr((long)size); - return MemoryAllocWindows.AllocateWriteTracked(sizeNint); + return MemoryManagementWindows.AllocateWriteTracked(sizeNint); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - return MemoryAllocUnix.Allocate(size); + return MemoryManagementUnix.Allocate(size); } else { @@ -54,12 +54,12 @@ namespace ChocolArm64.Memory { IntPtr sizeNint = new IntPtr((long)size); - result = MemoryAllocWindows.Reprotect(address, sizeNint, permission); + result = MemoryManagementWindows.Reprotect(address, sizeNint, permission); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - result = MemoryAllocUnix.Reprotect(address, size, permission); + result = MemoryManagementUnix.Reprotect(address, size, permission); } else { @@ -76,12 +76,12 @@ namespace ChocolArm64.Memory { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - return MemoryAllocWindows.Free(address); + return MemoryManagementWindows.Free(address); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - return MemoryAllocUnix.Free(address); + return MemoryManagementUnix.Free(address); } else { @@ -101,7 +101,7 @@ namespace ChocolArm64.Memory //write tracking support on the OS. if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - return MemoryAllocWindows.GetModifiedPages(address, size, addresses, out count); + return MemoryManagementWindows.GetModifiedPages(address, size, addresses, out count); } else { diff --git a/Memory/MemoryAllocUnix.cs b/Memory/MemoryManagementUnix.cs similarity index 98% rename from Memory/MemoryAllocUnix.cs rename to Memory/MemoryManagementUnix.cs index 857c1c5..9fe1aef 100644 --- a/Memory/MemoryAllocUnix.cs +++ b/Memory/MemoryManagementUnix.cs @@ -3,7 +3,7 @@ using System; namespace ChocolArm64.Memory { - static class MemoryAllocUnix + static class MemoryManagementUnix { public static IntPtr Allocate(ulong size) { diff --git a/Memory/MemoryAllocWindows.cs b/Memory/MemoryManagementWindows.cs similarity index 95% rename from Memory/MemoryAllocWindows.cs rename to Memory/MemoryManagementWindows.cs index 82be8b1..6cee134 100644 --- a/Memory/MemoryAllocWindows.cs +++ b/Memory/MemoryManagementWindows.cs @@ -4,7 +4,7 @@ using System.Runtime.InteropServices; namespace ChocolArm64.Memory { - static class MemoryAllocWindows + static class MemoryManagementWindows { [Flags] private enum AllocationType : uint @@ -21,7 +21,7 @@ namespace ChocolArm64.Memory } [Flags] - private enum MemoryProtection + private enum MemoryProtection : uint { NoAccess = 0x01, ReadOnly = 0x02, @@ -59,7 +59,7 @@ namespace ChocolArm64.Memory [DllImport("kernel32.dll")] private static extern bool VirtualFree( IntPtr lpAddress, - uint dwSize, + IntPtr dwSize, AllocationType dwFreeType); [DllImport("kernel32.dll")] @@ -127,7 +127,7 @@ namespace ChocolArm64.Memory public static bool Free(IntPtr address) { - return VirtualFree(address, 0, AllocationType.Release); + return VirtualFree(address, IntPtr.Zero, AllocationType.Release); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Memory/MemoryManager.cs b/Memory/MemoryManager.cs index afb0f65..ce102e0 100644 --- a/Memory/MemoryManager.cs +++ b/Memory/MemoryManager.cs @@ -1,8 +1,5 @@ -using ChocolArm64.Events; -using ChocolArm64.Exceptions; using ChocolArm64.Instructions; using System; -using System.Collections.Concurrent; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; @@ -10,52 +7,399 @@ using System.Runtime.Intrinsics.X86; using System.Threading; using static ChocolArm64.Memory.CompareExchange128; +using static ChocolArm64.Memory.MemoryManagement; namespace ChocolArm64.Memory { public unsafe class MemoryManager : IMemory, IDisposable { - private const int PtLvl0Bits = 13; - private const int PtLvl1Bits = 14; - public const int PageBits = 12; + public const int PageBits = 12; + public const int PageSize = 1 << PageBits; + public const int PageMask = PageSize - 1; - private const int PtLvl0Size = 1 << PtLvl0Bits; - private const int PtLvl1Size = 1 << PtLvl1Bits; - public const int PageSize = 1 << PageBits; + private const long PteFlagNotModified = 1; - private const int PtLvl0Mask = PtLvl0Size - 1; - private const int PtLvl1Mask = PtLvl1Size - 1; - public const int PageMask = PageSize - 1; - - private const int PtLvl0Bit = PageBits + PtLvl1Bits; - private const int PtLvl1Bit = PageBits; - - private ConcurrentDictionary _observedPages; + internal const long PteFlagsMask = 7; public IntPtr Ram { get; private set; } private byte* _ramPtr; - private byte*** _pageTable; + private IntPtr _pageTable; - public event EventHandler InvalidAccess; + internal IntPtr PageTable => _pageTable; - public event EventHandler ObservedAccess; + internal int PtLevelBits { get; } + internal int PtLevelSize { get; } + internal int PtLevelMask { get; } - public MemoryManager(IntPtr ram) + public bool HasWriteWatchSupport => MemoryManagement.HasWriteWatchSupport; + + public int AddressSpaceBits { get; } + public long AddressSpaceSize { get; } + + public MemoryManager( + IntPtr ram, + int addressSpaceBits = 48, + bool useFlatPageTable = false) { - _observedPages = new ConcurrentDictionary(); - Ram = ram; _ramPtr = (byte*)ram; - _pageTable = (byte***)Marshal.AllocHGlobal(PtLvl0Size * IntPtr.Size); + AddressSpaceBits = addressSpaceBits; + AddressSpaceSize = 1L << addressSpaceBits; - for (int l0 = 0; l0 < PtLvl0Size; l0++) + //When flat page table is requested, we use a single + //array for the mappings of the entire address space. + //This has better performance, but also high memory usage. + //The multi level page table uses 9 bits per level, so + //the memory usage is lower, but the performance is also + //lower, since each address translation requires multiple reads. + if (useFlatPageTable) { - _pageTable[l0] = null; + PtLevelBits = addressSpaceBits - PageBits; } + else + { + PtLevelBits = 9; + } + + PtLevelSize = 1 << PtLevelBits; + PtLevelMask = PtLevelSize - 1; + + _pageTable = Allocate((ulong)(PtLevelSize * IntPtr.Size)); + } + + public void Map(long va, long pa, long size) + { + SetPtEntries(va, _ramPtr + pa, size); + } + + public void Unmap(long position, long size) + { + SetPtEntries(position, null, size); + } + + public bool IsMapped(long position) + { + return Translate(position) != IntPtr.Zero; + } + + public long GetPhysicalAddress(long virtualAddress) + { + byte* ptr = (byte*)Translate(virtualAddress); + + return (long)(ptr - _ramPtr); + } + + private IntPtr Translate(long position) + { + if (!IsValidPosition(position)) + { + return IntPtr.Zero; + } + + byte* ptr = GetPtEntry(position); + + ulong ptrUlong = (ulong)ptr; + + if ((ptrUlong & PteFlagsMask) != 0) + { + ptrUlong &= ~(ulong)PteFlagsMask; + + ptr = (byte*)ptrUlong; + } + + return new IntPtr(ptr + (position & PageMask)); + } + + private IntPtr TranslateWrite(long position) + { + if (!IsValidPosition(position)) + { + return IntPtr.Zero; + } + + byte* ptr = GetPtEntry(position); + + ulong ptrUlong = (ulong)ptr; + + if ((ptrUlong & PteFlagsMask) != 0) + { + if ((ptrUlong & PteFlagNotModified) != 0) + { + ClearPtEntryFlag(position, PteFlagNotModified); + } + + ptrUlong &= ~(ulong)PteFlagsMask; + + ptr = (byte*)ptrUlong; + } + + return new IntPtr(ptr + (position & PageMask)); + } + + private byte* GetPtEntry(long position) + { + return *(byte**)GetPtPtr(position); + } + + private void SetPtEntries(long va, byte* ptr, long size) + { + long endPosition = (va + size + PageMask) & ~PageMask; + + while ((ulong)va < (ulong)endPosition) + { + SetPtEntry(va, ptr); + + va += PageSize; + + if (ptr != null) + { + ptr += PageSize; + } + } + } + + private void SetPtEntry(long position, byte* ptr) + { + *(byte**)GetPtPtr(position) = ptr; + } + + private void SetPtEntryFlag(long position, long flag) + { + ModifyPtEntryFlag(position, flag, setFlag: true); + } + + private void ClearPtEntryFlag(long position, long flag) + { + ModifyPtEntryFlag(position, flag, setFlag: false); + } + + private void ModifyPtEntryFlag(long position, long flag, bool setFlag) + { + IntPtr* pt = (IntPtr*)_pageTable; + + while (true) + { + IntPtr* ptPtr = GetPtPtr(position); + + IntPtr old = *ptPtr; + + long modified = old.ToInt64(); + + if (setFlag) + { + modified |= flag; + } + else + { + modified &= ~flag; + } + + IntPtr origValue = Interlocked.CompareExchange(ref *ptPtr, new IntPtr(modified), old); + + if (origValue == old) + { + break; + } + } + } + + private IntPtr* GetPtPtr(long position) + { + if (!IsValidPosition(position)) + { + throw new ArgumentOutOfRangeException(nameof(position)); + } + + IntPtr nextPtr = _pageTable; + + IntPtr* ptePtr = null; + + int bit = PageBits; + + while (true) + { + long index = (position >> bit) & PtLevelMask; + + ptePtr = &((IntPtr*)nextPtr)[index]; + + bit += PtLevelBits; + + if (bit >= AddressSpaceBits) + { + break; + } + + nextPtr = *ptePtr; + + if (nextPtr == IntPtr.Zero) + { + //Entry does not yet exist, allocate a new one. + IntPtr newPtr = Allocate((ulong)(PtLevelSize * IntPtr.Size)); + + //Try to swap the current pointer (should be zero), with the allocated one. + nextPtr = Interlocked.Exchange(ref *ptePtr, newPtr); + + //If the old pointer is not null, then another thread already has set it. + if (nextPtr != IntPtr.Zero) + { + Free(newPtr); + } + else + { + nextPtr = newPtr; + } + } + } + + return ptePtr; + } + + public bool IsRegionModified(long position, long size) + { + if (!HasWriteWatchSupport) + { + return IsRegionModifiedFallback(position, size); + } + + IntPtr address = Translate(position); + + IntPtr baseAddr = address; + IntPtr expectedAddr = address; + + long pendingPages = 0; + + long pages = size / PageSize; + + bool modified = false; + + bool IsAnyPageModified() + { + IntPtr pendingSize = new IntPtr(pendingPages * PageSize); + + IntPtr[] addresses = new IntPtr[pendingPages]; + + bool result = GetModifiedPages(baseAddr, pendingSize, addresses, out ulong count); + + if (result) + { + return count != 0; + } + else + { + return true; + } + } + + while (pages-- > 0) + { + if (address != expectedAddr) + { + modified |= IsAnyPageModified(); + + baseAddr = address; + + pendingPages = 0; + } + + expectedAddr = address + PageSize; + + pendingPages++; + + if (pages == 0) + { + break; + } + + position += PageSize; + + address = Translate(position); + } + + if (pendingPages != 0) + { + modified |= IsAnyPageModified(); + } + + return modified; + } + + private unsafe bool IsRegionModifiedFallback(long position, long size) + { + long endAddr = (position + size + PageMask) & ~PageMask; + + bool modified = false; + + while ((ulong)position < (ulong)endAddr) + { + if (IsValidPosition(position)) + { + byte* ptr = ((byte**)_pageTable)[position >> PageBits]; + + ulong ptrUlong = (ulong)ptr; + + if ((ptrUlong & PteFlagNotModified) == 0) + { + modified = true; + + SetPtEntryFlag(position, PteFlagNotModified); + } + } + else + { + modified = true; + } + + position += PageSize; + } + + return modified; + } + + public bool TryGetHostAddress(long position, long size, out IntPtr ptr) + { + if (IsContiguous(position, size)) + { + ptr = (IntPtr)Translate(position); + + return true; + } + + ptr = IntPtr.Zero; + + return false; + } + + private bool IsContiguous(long position, long size) + { + long endPos = position + size; + + position &= ~PageMask; + + long expectedPa = GetPhysicalAddress(position); + + while ((ulong)position < (ulong)endPos) + { + long pa = GetPhysicalAddress(position); + + if (pa != expectedPa) + { + return false; + } + + position += PageSize; + expectedPa += PageSize; + } + + return true; + } + + public bool IsValidPosition(long position) + { + return (ulong)position < (ulong)AddressSpaceSize; } internal bool AtomicCompareExchange2xInt32( @@ -86,7 +430,7 @@ namespace ChocolArm64.Memory AbortWithAlignmentFault(position); } - IntPtr ptr = new IntPtr(TranslateWrite(position)); + IntPtr ptr = TranslateWrite(position); return InterlockedCompareExchange128(ptr, expectedLow, expectedHigh, desiredLow, desiredHigh); } @@ -98,7 +442,7 @@ namespace ChocolArm64.Memory AbortWithAlignmentFault(position); } - IntPtr ptr = new IntPtr(Translate(position)); + IntPtr ptr = Translate(position); InterlockedRead128(ptr, out ulong low, out ulong high); @@ -371,7 +715,7 @@ namespace ChocolArm64.Memory int copySize = (int)(pageLimit - position); - Marshal.Copy((IntPtr)Translate(position), data, offset, copySize); + Marshal.Copy(Translate(position), data, offset, copySize); position += copySize; offset += copySize; @@ -408,7 +752,7 @@ namespace ChocolArm64.Memory int copySize = (int)(pageLimit - position); - Marshal.Copy((IntPtr)Translate(position), data, offset, copySize); + Marshal.Copy(Translate(position), data, offset, copySize); position += copySize; offset += copySize; @@ -571,7 +915,7 @@ namespace ChocolArm64.Memory int copySize = (int)(pageLimit - position); - Marshal.Copy(data, offset, (IntPtr)TranslateWrite(position), copySize); + Marshal.Copy(data, offset, TranslateWrite(position), copySize); position += copySize; offset += copySize; @@ -601,7 +945,7 @@ namespace ChocolArm64.Memory int copySize = (int)(pageLimit - position); - Marshal.Copy(data, offset, (IntPtr)TranslateWrite(position), copySize); + Marshal.Copy(data, offset, Translate(position), copySize); position += copySize; offset += copySize; @@ -614,8 +958,8 @@ namespace ChocolArm64.Memory if (IsContiguous(src, size) && IsContiguous(dst, size)) { - byte* srcPtr = Translate(src); - byte* dstPtr = TranslateWrite(dst); + byte* srcPtr = (byte*)Translate(src); + byte* dstPtr = (byte*)Translate(dst); Buffer.MemoryCopy(srcPtr, dstPtr, size, size); } @@ -625,266 +969,6 @@ namespace ChocolArm64.Memory } } - public void Map(long va, long pa, long size) - { - SetPtEntries(va, _ramPtr + pa, size); - } - - public void Unmap(long position, long size) - { - SetPtEntries(position, null, size); - - StopObservingRegion(position, size); - } - - public bool IsMapped(long position) - { - if (!(IsValidPosition(position))) - { - return false; - } - - long l0 = (position >> PtLvl0Bit) & PtLvl0Mask; - long l1 = (position >> PtLvl1Bit) & PtLvl1Mask; - - if (_pageTable[l0] == null) - { - return false; - } - - return _pageTable[l0][l1] != null || _observedPages.ContainsKey(position >> PageBits); - } - - public long GetPhysicalAddress(long virtualAddress) - { - byte* ptr = Translate(virtualAddress); - - return (long)(ptr - _ramPtr); - } - - internal byte* Translate(long position) - { - long l0 = (position >> PtLvl0Bit) & PtLvl0Mask; - long l1 = (position >> PtLvl1Bit) & PtLvl1Mask; - - long old = position; - - byte** lvl1 = _pageTable[l0]; - - if ((position >> (PtLvl0Bit + PtLvl0Bits)) != 0) - { - goto Unmapped; - } - - if (lvl1 == null) - { - goto Unmapped; - } - - position &= PageMask; - - byte* ptr = lvl1[l1]; - - if (ptr == null) - { - goto Unmapped; - } - - return ptr + position; - -Unmapped: - return HandleNullPte(old); - } - - private byte* HandleNullPte(long position) - { - long key = position >> PageBits; - - if (_observedPages.TryGetValue(key, out IntPtr ptr)) - { - return (byte*)ptr + (position & PageMask); - } - - InvalidAccess?.Invoke(this, new MemoryAccessEventArgs(position)); - - throw new VmmPageFaultException(position); - } - - internal byte* TranslateWrite(long position) - { - long l0 = (position >> PtLvl0Bit) & PtLvl0Mask; - long l1 = (position >> PtLvl1Bit) & PtLvl1Mask; - - long old = position; - - byte** lvl1 = _pageTable[l0]; - - if ((position >> (PtLvl0Bit + PtLvl0Bits)) != 0) - { - goto Unmapped; - } - - if (lvl1 == null) - { - goto Unmapped; - } - - position &= PageMask; - - byte* ptr = lvl1[l1]; - - if (ptr == null) - { - goto Unmapped; - } - - return ptr + position; - -Unmapped: - return HandleNullPteWrite(old); - } - - private byte* HandleNullPteWrite(long position) - { - long key = position >> PageBits; - - MemoryAccessEventArgs e = new MemoryAccessEventArgs(position); - - if (_observedPages.TryGetValue(key, out IntPtr ptr)) - { - SetPtEntry(position, (byte*)ptr); - - ObservedAccess?.Invoke(this, e); - - return (byte*)ptr + (position & PageMask); - } - - InvalidAccess?.Invoke(this, e); - - throw new VmmPageFaultException(position); - } - - private void SetPtEntries(long va, byte* ptr, long size) - { - long endPosition = (va + size + PageMask) & ~PageMask; - - while ((ulong)va < (ulong)endPosition) - { - SetPtEntry(va, ptr); - - va += PageSize; - - if (ptr != null) - { - ptr += PageSize; - } - } - } - - private void SetPtEntry(long position, byte* ptr) - { - if (!IsValidPosition(position)) - { - throw new ArgumentOutOfRangeException(nameof(position)); - } - - long l0 = (position >> PtLvl0Bit) & PtLvl0Mask; - long l1 = (position >> PtLvl1Bit) & PtLvl1Mask; - - if (_pageTable[l0] == null) - { - byte** lvl1 = (byte**)Marshal.AllocHGlobal(PtLvl1Size * IntPtr.Size); - - for (int zl1 = 0; zl1 < PtLvl1Size; zl1++) - { - lvl1[zl1] = null; - } - - Thread.MemoryBarrier(); - - _pageTable[l0] = lvl1; - } - - _pageTable[l0][l1] = ptr; - } - - public void StartObservingRegion(long position, long size) - { - long endPosition = (position + size + PageMask) & ~PageMask; - - position &= ~PageMask; - - while ((ulong)position < (ulong)endPosition) - { - _observedPages[position >> PageBits] = (IntPtr)Translate(position); - - SetPtEntry(position, null); - - position += PageSize; - } - } - - public void StopObservingRegion(long position, long size) - { - long endPosition = (position + size + PageMask) & ~PageMask; - - while (position < endPosition) - { - lock (_observedPages) - { - if (_observedPages.TryRemove(position >> PageBits, out IntPtr ptr)) - { - SetPtEntry(position, (byte*)ptr); - } - } - - position += PageSize; - } - } - - public bool TryGetHostAddress(long position, long size, out IntPtr ptr) - { - if (IsContiguous(position, size)) - { - ptr = (IntPtr)Translate(position); - - return true; - } - - ptr = IntPtr.Zero; - - return false; - } - - private bool IsContiguous(long position, long size) - { - long endPos = position + size; - - position &= ~PageMask; - - long expectedPa = GetPhysicalAddress(position); - - while ((ulong)position < (ulong)endPos) - { - long pa = GetPhysicalAddress(position); - - if (pa != expectedPa) - { - return false; - } - - position += PageSize; - expectedPa += PageSize; - } - - return true; - } - - public bool IsValidPosition(long position) - { - return position >> (PtLvl0Bits + PtLvl1Bits + PageBits) == 0; - } - public void Dispose() { Dispose(true); @@ -892,24 +976,36 @@ Unmapped: protected virtual void Dispose(bool disposing) { - if (_pageTable == null) + IntPtr ptr = Interlocked.Exchange(ref _pageTable, IntPtr.Zero); + + if (ptr != IntPtr.Zero) { + FreePageTableEntry(ptr, PageBits); + } + } + + private void FreePageTableEntry(IntPtr ptr, int levelBitEnd) + { + levelBitEnd += PtLevelBits; + + if (levelBitEnd >= AddressSpaceBits) + { + Free(ptr); + return; } - for (int l0 = 0; l0 < PtLvl0Size; l0++) + for (int index = 0; index < PtLevelSize; index++) { - if (_pageTable[l0] != null) - { - Marshal.FreeHGlobal((IntPtr)_pageTable[l0]); - } + IntPtr ptePtr = ((IntPtr*)ptr)[index]; - _pageTable[l0] = null; + if (ptePtr != IntPtr.Zero) + { + FreePageTableEntry(ptePtr, levelBitEnd); + } } - Marshal.FreeHGlobal((IntPtr)_pageTable); - - _pageTable = null; + Free(ptr); } } } \ No newline at end of file diff --git a/Translation/ILEmitterCtx.cs b/Translation/ILEmitterCtx.cs index 5490123..f7e61bc 100644 --- a/Translation/ILEmitterCtx.cs +++ b/Translation/ILEmitterCtx.cs @@ -1,5 +1,6 @@ using ChocolArm64.Decoders; using ChocolArm64.Instructions; +using ChocolArm64.Memory; using ChocolArm64.State; using System; using System.Collections.Generic; @@ -10,6 +11,8 @@ namespace ChocolArm64.Translation { class ILEmitterCtx { + public MemoryManager Memory { get; } + private TranslatorCache _cache; private TranslatorQueue _queue; @@ -43,19 +46,34 @@ namespace ChocolArm64.Translation //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; - private const int IntTmp2Index = -7; + private const int ReservedLocalsCount = 64; - public ILEmitterCtx(TranslatorCache cache, TranslatorQueue queue, TranslationTier tier, Block graph) + private const int RorTmpIndex = ReservedLocalsCount + 0; + private const int CmpOptTmp1Index = ReservedLocalsCount + 1; + private const int CmpOptTmp2Index = ReservedLocalsCount + 2; + private const int IntGpTmp1Index = ReservedLocalsCount + 3; + private const int IntGpTmp2Index = ReservedLocalsCount + 4; + private const int UserIntTempStart = ReservedLocalsCount + 5; + + //Vectors are part of another "set" of locals. + private const int VecGpTmp1Index = ReservedLocalsCount + 0; + private const int VecGpTmp2Index = ReservedLocalsCount + 1; + private const int UserVecTempStart = ReservedLocalsCount + 2; + + private static int _userIntTempCount; + private static int _userVecTempCount; + + public ILEmitterCtx( + MemoryManager memory, + TranslatorCache cache, + TranslatorQueue queue, + TranslationTier tier, + Block graph) { - _cache = cache ?? throw new ArgumentNullException(nameof(cache)); - _queue = queue ?? throw new ArgumentNullException(nameof(queue)); - _currBlock = graph ?? throw new ArgumentNullException(nameof(graph)); + Memory = memory ?? throw new ArgumentNullException(nameof(memory)); + _cache = cache ?? throw new ArgumentNullException(nameof(cache)); + _queue = queue ?? throw new ArgumentNullException(nameof(queue)); + _currBlock = graph ?? throw new ArgumentNullException(nameof(graph)); Tier = tier; @@ -76,6 +94,16 @@ namespace ChocolArm64.Translation AdvanceOpCode(); } + public static int GetIntTempIndex() + { + return UserIntTempStart + _userIntTempCount++; + } + + public static int GetVecTempIndex() + { + return UserVecTempStart + _userVecTempCount++; + } + public ILBlock[] GetILBlocks() { EmitAllOpCodes(); @@ -145,7 +173,7 @@ namespace ChocolArm64.Translation _ilBlock.Add(new ILBarrier()); } - private Condition GetInverseCond(Condition cond) + private static Condition GetInverseCond(Condition cond) { //Bit 0 of all conditions is basically a negation bit, so //inverting this bit has the effect of inverting the condition. @@ -560,17 +588,17 @@ namespace ChocolArm64.Translation _ilBlock.Add(new ILOpCodeStoreState(_ilBlock)); } - public void EmitLdtmp() => EmitLdint(IntTmpIndex); - public void EmitSttmp() => EmitStint(IntTmpIndex); + public void EmitLdtmp() => EmitLdint(IntGpTmp1Index); + public void EmitSttmp() => EmitStint(IntGpTmp1Index); - public void EmitLdtmp2() => EmitLdint(IntTmp2Index); - public void EmitSttmp2() => EmitStint(IntTmp2Index); + public void EmitLdtmp2() => EmitLdint(IntGpTmp2Index); + public void EmitSttmp2() => EmitStint(IntGpTmp2Index); - public void EmitLdvectmp() => EmitLdvec(VecTmp1Index); - public void EmitStvectmp() => EmitStvec(VecTmp1Index); + public void EmitLdvectmp() => EmitLdvec(VecGpTmp1Index); + public void EmitStvectmp() => EmitStvec(VecGpTmp1Index); - public void EmitLdvectmp2() => EmitLdvec(VecTmp2Index); - public void EmitStvectmp2() => EmitStvec(VecTmp2Index); + public void EmitLdvectmp2() => EmitLdvec(VecGpTmp2Index); + public void EmitStvectmp2() => EmitStvec(VecGpTmp2Index); public void EmitLdint(int index) => Ldloc(index, IoType.Int); public void EmitStint(int index) => Stloc(index, IoType.Int); @@ -611,62 +639,12 @@ namespace ChocolArm64.Translation 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}")); + EmitCall(objType, $"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 EmitCallPrivatePropGet(Type objType, string propName) - { - if (objType == null) - { - throw new ArgumentNullException(nameof(objType)); - } - - if (propName == null) - { - throw new ArgumentNullException(nameof(propName)); - } - - EmitPrivateCall(objType, $"get_{propName}"); - } - - public void EmitCallPrivatePropSet(Type objType, string propName) - { - if (objType == null) - { - throw new ArgumentNullException(nameof(objType)); - } - - if (propName == null) - { - throw new ArgumentNullException(nameof(propName)); - } - - EmitPrivateCall(objType, $"set_{propName}"); + EmitCall(objType, $"set_{propName}"); } public void EmitCall(Type objType, string mthdName) @@ -684,6 +662,16 @@ namespace ChocolArm64.Translation EmitCall(objType.GetMethod(mthdName)); } + public void EmitCallPrivatePropGet(Type objType, string propName) + { + EmitPrivateCall(objType, $"get_{propName}"); + } + + public void EmitCallPrivatePropSet(Type objType, string propName) + { + EmitPrivateCall(objType, $"set_{propName}"); + } + public void EmitPrivateCall(Type objType, string mthdName) { if (objType == null) diff --git a/Translation/Translator.cs b/Translation/Translator.cs index 7f7df6e..dd1215f 100644 --- a/Translation/Translator.cs +++ b/Translation/Translator.cs @@ -138,7 +138,7 @@ namespace ChocolArm64.Translation { Block block = Decoder.DecodeBasicBlock(_memory, position, mode); - ILEmitterCtx context = new ILEmitterCtx(_cache, _queue, TranslationTier.Tier0, block); + ILEmitterCtx context = new ILEmitterCtx(_memory, _cache, _queue, TranslationTier.Tier0, block); string subName = GetSubroutineName(position); @@ -153,7 +153,7 @@ namespace ChocolArm64.Translation { Block graph = Decoder.DecodeSubroutine(_memory, position, mode); - ILEmitterCtx context = new ILEmitterCtx(_cache, _queue, TranslationTier.Tier1, graph); + ILEmitterCtx context = new ILEmitterCtx(_memory, _cache, _queue, TranslationTier.Tier1, graph); ILBlock[] ilBlocks = context.GetILBlocks();