aboutsummaryrefslogtreecommitdiff
path: root/ARMeilleure/Translation
diff options
context:
space:
mode:
Diffstat (limited to 'ARMeilleure/Translation')
-rw-r--r--ARMeilleure/Translation/ArmEmitterContext.cs2
-rw-r--r--ARMeilleure/Translation/Compiler.cs20
-rw-r--r--ARMeilleure/Translation/DelegateCache.cs26
-rw-r--r--ARMeilleure/Translation/DelegateHelper.cs107
-rw-r--r--ARMeilleure/Translation/DelegateInfo.cs19
-rw-r--r--ARMeilleure/Translation/Delegates.cs305
-rw-r--r--ARMeilleure/Translation/DirectCallStubs.cs44
-rw-r--r--ARMeilleure/Translation/EmitterContext.cs74
-rw-r--r--ARMeilleure/Translation/JitCache.cs17
-rw-r--r--ARMeilleure/Translation/JitUnwindWindows.cs16
-rw-r--r--ARMeilleure/Translation/JumpTable.cs156
-rw-r--r--ARMeilleure/Translation/PTC/EncodingCache.cs9
-rw-r--r--ARMeilleure/Translation/PTC/Ptc.cs768
-rw-r--r--ARMeilleure/Translation/PTC/PtcInfo.cs68
-rw-r--r--ARMeilleure/Translation/PTC/PtcJumpTable.cs222
-rw-r--r--ARMeilleure/Translation/PTC/PtcProfiler.cs267
-rw-r--r--ARMeilleure/Translation/PTC/PtcState.cs10
-rw-r--r--ARMeilleure/Translation/PTC/RelocEntry.cs19
-rw-r--r--ARMeilleure/Translation/PriorityQueue.cs39
-rw-r--r--ARMeilleure/Translation/RejitRequest.cs2
-rw-r--r--ARMeilleure/Translation/TranslatedFunction.cs31
-rw-r--r--ARMeilleure/Translation/Translator.cs92
22 files changed, 2085 insertions, 228 deletions
diff --git a/ARMeilleure/Translation/ArmEmitterContext.cs b/ARMeilleure/Translation/ArmEmitterContext.cs
index a905c722..7ce6a3f4 100644
--- a/ARMeilleure/Translation/ArmEmitterContext.cs
+++ b/ARMeilleure/Translation/ArmEmitterContext.cs
@@ -11,7 +11,7 @@ namespace ARMeilleure.Translation
{
class ArmEmitterContext : EmitterContext
{
- private Dictionary<ulong, Operand> _labels;
+ private readonly Dictionary<ulong, Operand> _labels;
private OpCode _optOpLastCompare;
private OpCode _optOpLastFlagSet;
diff --git a/ARMeilleure/Translation/Compiler.cs b/ARMeilleure/Translation/Compiler.cs
index ec2f2968..934c0db6 100644
--- a/ARMeilleure/Translation/Compiler.cs
+++ b/ARMeilleure/Translation/Compiler.cs
@@ -7,18 +7,30 @@ using System.Runtime.InteropServices;
namespace ARMeilleure.Translation
{
+ using PTC;
+
static class Compiler
{
- public static T Compile<T>(ControlFlowGraph cfg, OperandType[] argTypes, OperandType retType, CompilerOptions options)
+ public static T Compile<T>(
+ ControlFlowGraph cfg,
+ OperandType[] argTypes,
+ OperandType retType,
+ CompilerOptions options,
+ PtcInfo ptcInfo = null)
{
- CompiledFunction func = Compile(cfg, argTypes, retType, options);
+ CompiledFunction func = Compile(cfg, argTypes, retType, options, ptcInfo);
IntPtr codePtr = JitCache.Map(func);
return Marshal.GetDelegateForFunctionPointer<T>(codePtr);
}
- public static CompiledFunction Compile(ControlFlowGraph cfg, OperandType[] argTypes, OperandType retType, CompilerOptions options)
+ public static CompiledFunction Compile(
+ ControlFlowGraph cfg,
+ OperandType[] argTypes,
+ OperandType retType,
+ CompilerOptions options,
+ PtcInfo ptcInfo = null)
{
Logger.StartPass(PassName.Dominance);
@@ -45,7 +57,7 @@ namespace ARMeilleure.Translation
CompilerContext cctx = new CompilerContext(cfg, argTypes, retType, options);
- return CodeGenerator.Generate(cctx);
+ return CodeGenerator.Generate(cctx, ptcInfo);
}
}
} \ No newline at end of file
diff --git a/ARMeilleure/Translation/DelegateCache.cs b/ARMeilleure/Translation/DelegateCache.cs
deleted file mode 100644
index 7328c61a..00000000
--- a/ARMeilleure/Translation/DelegateCache.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Reflection;
-
-namespace ARMeilleure.Translation
-{
- static class DelegateCache
- {
- private static ConcurrentDictionary<string, Delegate> _delegates;
-
- static DelegateCache()
- {
- _delegates = new ConcurrentDictionary<string, Delegate>();
- }
-
- public static Delegate GetOrAdd(Delegate dlg)
- {
- return _delegates.GetOrAdd(GetKey(dlg.Method), (key) => dlg);
- }
-
- private static string GetKey(MethodInfo info)
- {
- return $"{info.DeclaringType.FullName}.{info.Name}";
- }
- }
-} \ No newline at end of file
diff --git a/ARMeilleure/Translation/DelegateHelper.cs b/ARMeilleure/Translation/DelegateHelper.cs
new file mode 100644
index 00000000..f021d116
--- /dev/null
+++ b/ARMeilleure/Translation/DelegateHelper.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace ARMeilleure.Translation
+{
+ static class DelegateHelper
+ {
+ private const string DelegateTypesAssemblyName = "JitDelegateTypes";
+
+ private static readonly ModuleBuilder _modBuilder;
+
+ private static readonly Dictionary<string, Type> _delegateTypesCache;
+
+ static DelegateHelper()
+ {
+ AssemblyBuilder asmBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(DelegateTypesAssemblyName), AssemblyBuilderAccess.Run);
+
+ _modBuilder = asmBuilder.DefineDynamicModule(DelegateTypesAssemblyName);
+
+ _delegateTypesCache = new Dictionary<string, Type>();
+ }
+
+ public static Delegate GetDelegate(MethodInfo info)
+ {
+ if (info == null)
+ {
+ throw new ArgumentNullException(nameof(info));
+ }
+
+ Type[] parameters = info.GetParameters().Select(pI => pI.ParameterType).ToArray();
+ Type returnType = info.ReturnType;
+
+ Type delegateType = GetDelegateType(parameters, returnType);
+
+ return Delegate.CreateDelegate(delegateType, info);
+ }
+
+ private static Type GetDelegateType(Type[] parameters, Type returnType)
+ {
+ string key = GetFunctionSignatureKey(parameters, returnType);
+
+ if (!_delegateTypesCache.TryGetValue(key, out Type delegateType))
+ {
+ delegateType = MakeDelegateType(parameters, returnType, key);
+
+ _delegateTypesCache.TryAdd(key, delegateType);
+ }
+
+ return delegateType;
+ }
+
+ private static string GetFunctionSignatureKey(Type[] parameters, Type returnType)
+ {
+ string sig = GetTypeName(returnType);
+
+ foreach (Type type in parameters)
+ {
+ sig += '_' + GetTypeName(type);
+ }
+
+ return sig;
+ }
+
+ private static string GetTypeName(Type type)
+ {
+ return type.FullName.Replace(".", string.Empty);
+ }
+
+ private const MethodAttributes CtorAttributes =
+ MethodAttributes.RTSpecialName |
+ MethodAttributes.HideBySig |
+ MethodAttributes.Public;
+
+ private const TypeAttributes DelegateTypeAttributes =
+ TypeAttributes.Class |
+ TypeAttributes.Public |
+ TypeAttributes.Sealed |
+ TypeAttributes.AnsiClass |
+ TypeAttributes.AutoClass;
+
+ private const MethodImplAttributes ImplAttributes =
+ MethodImplAttributes.Runtime |
+ MethodImplAttributes.Managed;
+
+ private const MethodAttributes InvokeAttributes =
+ MethodAttributes.Public |
+ MethodAttributes.HideBySig |
+ MethodAttributes.NewSlot |
+ MethodAttributes.Virtual;
+
+ private static readonly Type[] _delegateCtorSignature = { typeof(object), typeof(IntPtr) };
+
+ private static Type MakeDelegateType(Type[] parameters, Type returnType, string name)
+ {
+ TypeBuilder builder = _modBuilder.DefineType(name, DelegateTypeAttributes, typeof(MulticastDelegate));
+
+ builder.DefineConstructor(CtorAttributes, CallingConventions.Standard, _delegateCtorSignature).SetImplementationFlags(ImplAttributes);
+
+ builder.DefineMethod("Invoke", InvokeAttributes, returnType, parameters).SetImplementationFlags(ImplAttributes);
+
+ return builder.CreateTypeInfo();
+ }
+ }
+}
diff --git a/ARMeilleure/Translation/DelegateInfo.cs b/ARMeilleure/Translation/DelegateInfo.cs
new file mode 100644
index 00000000..e68cfc1b
--- /dev/null
+++ b/ARMeilleure/Translation/DelegateInfo.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace ARMeilleure.Translation
+{
+ sealed class DelegateInfo
+ {
+ private readonly Delegate _dlg; // Ensure that this delegate will not be garbage collected.
+
+ public IntPtr FuncPtr { get; }
+
+ public DelegateInfo(Delegate dlg)
+ {
+ _dlg = dlg;
+
+ FuncPtr = Marshal.GetFunctionPointerForDelegate<Delegate>(dlg);
+ }
+ }
+}
diff --git a/ARMeilleure/Translation/Delegates.cs b/ARMeilleure/Translation/Delegates.cs
new file mode 100644
index 00000000..addb15f6
--- /dev/null
+++ b/ARMeilleure/Translation/Delegates.cs
@@ -0,0 +1,305 @@
+using ARMeilleure.Instructions;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace ARMeilleure.Translation
+{
+ static class Delegates
+ {
+ public static bool TryGetDelegateFuncPtrByIndex(int index, out IntPtr funcPtr)
+ {
+ if (index >= 0 && index < _delegates.Count)
+ {
+ funcPtr = _delegates.Values[index].FuncPtr; // O(1).
+
+ return true;
+ }
+ else
+ {
+ funcPtr = default;
+
+ return false;
+ }
+ }
+
+ public static IntPtr GetDelegateFuncPtrByIndex(int index)
+ {
+ if (index < 0 || index >= _delegates.Count)
+ {
+ throw new ArgumentOutOfRangeException($"({nameof(index)} = {index})");
+ }
+
+ return _delegates.Values[index].FuncPtr; // O(1).
+ }
+
+ public static IntPtr GetDelegateFuncPtr(MethodInfo info)
+ {
+ if (info == null)
+ {
+ throw new ArgumentNullException(nameof(info));
+ }
+
+ string key = GetKey(info);
+
+ if (!_delegates.TryGetValue(key, out DelegateInfo dlgInfo)) // O(log(n)).
+ {
+ throw new KeyNotFoundException($"({nameof(key)} = {key})");
+ }
+
+ return dlgInfo.FuncPtr;
+ }
+
+ public static int GetDelegateIndex(MethodInfo info)
+ {
+ if (info == null)
+ {
+ throw new ArgumentNullException(nameof(info));
+ }
+
+ string key = GetKey(info);
+
+ int index = _delegates.IndexOfKey(key); // O(log(n)).
+
+ if (index == -1)
+ {
+ throw new KeyNotFoundException($"({nameof(key)} = {key})");
+ }
+
+ return index;
+ }
+
+ private static void SetDelegateInfo(MethodInfo info)
+ {
+ string key = GetKey(info);
+
+ Delegate dlg = DelegateHelper.GetDelegate(info);
+
+ _delegates.Add(key, new DelegateInfo(dlg)); // ArgumentException (key).
+ }
+
+ private static string GetKey(MethodInfo info)
+ {
+ return $"{info.DeclaringType.Name}.{info.Name}";
+ }
+
+ private static readonly SortedList<string, DelegateInfo> _delegates;
+
+ static Delegates()
+ {
+ _delegates = new SortedList<string, DelegateInfo>();
+
+ SetDelegateInfo(typeof(Math).GetMethod(nameof(Math.Abs), new Type[] { typeof(double) }));
+ SetDelegateInfo(typeof(Math).GetMethod(nameof(Math.Ceiling), new Type[] { typeof(double) }));
+ SetDelegateInfo(typeof(Math).GetMethod(nameof(Math.Floor), new Type[] { typeof(double) }));
+ SetDelegateInfo(typeof(Math).GetMethod(nameof(Math.Round), new Type[] { typeof(double), typeof(MidpointRounding) }));
+ SetDelegateInfo(typeof(Math).GetMethod(nameof(Math.Truncate), new Type[] { typeof(double) }));
+
+ SetDelegateInfo(typeof(MathF).GetMethod(nameof(MathF.Abs), new Type[] { typeof(float) }));
+ SetDelegateInfo(typeof(MathF).GetMethod(nameof(MathF.Ceiling), new Type[] { typeof(float) }));
+ SetDelegateInfo(typeof(MathF).GetMethod(nameof(MathF.Floor), new Type[] { typeof(float) }));
+ SetDelegateInfo(typeof(MathF).GetMethod(nameof(MathF.Round), new Type[] { typeof(float), typeof(MidpointRounding) }));
+ SetDelegateInfo(typeof(MathF).GetMethod(nameof(MathF.Truncate), new Type[] { typeof(float) }));
+
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.Break)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.CheckSynchronization)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ClearExclusive)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntfrqEl0)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntpctEl0)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntvctEl0)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCtrEl0)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetDczidEl0)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpcr)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpscr))); // A32 only.
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFpsr)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetIndirectFunctionAddress)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidr32))); // A32 only.
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl0)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetTpidrEl032))); // A32 only.
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadByte)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadByteExclusive)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16Exclusive)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32Exclusive)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64Exclusive)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadVector128)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadVector128Exclusive)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetFpcr)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetFpscr))); // A32 only.
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetFpsr)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl0)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SetTpidrEl032))); // A32 only.
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SupervisorCall)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.Undefined)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteByte)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteByteExclusive)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt16)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt16Exclusive)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt32)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt32Exclusive)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt64)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt64Exclusive)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteVector128)));
+ SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteVector128Exclusive)));
+
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.BinarySignedSatQAcc)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.BinarySignedSatQAdd)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.BinarySignedSatQSub)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.BinaryUnsignedSatQAcc)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.BinaryUnsignedSatQAdd)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.BinaryUnsignedSatQSub)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.CountLeadingSigns)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.CountLeadingZeros)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.CountSetBits8)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32b)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32cb)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32ch)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32cw)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32cx)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32h)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32w)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32x)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Decrypt)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.DoubleToInt32))); // A32 only.
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.DoubleToUInt32))); // A32 only.
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Encrypt)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.FixedRotate)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.FloatToInt32))); // A32 only.
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.FloatToUInt32))); // A32 only.
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashChoose)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashLower)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashMajority)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashParity)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashUpper)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.InverseMixColumns)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.MixColumns)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Round)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.RoundF)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS32)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS64)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU32)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU64)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS32)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS64)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU32)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU64)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha1SchedulePart1)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha1SchedulePart2)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha256SchedulePart1)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha256SchedulePart2)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SignedShlReg)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SignedShlRegSatQ)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SignedShrImm64)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SignedSrcSignedDstSatQ)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SignedSrcUnsignedDstSatQ)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl1)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl2)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl3)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl4)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx1)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx2)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx3)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx4)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.UnarySignedSatQAbsOrNeg)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.UnsignedShlReg)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.UnsignedShlRegSatQ)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.UnsignedShrImm64)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.UnsignedSrcSignedDstSatQ)));
+ SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.UnsignedSrcUnsignedDstSatQ)));
+
+ SetDelegateInfo(typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert)));
+
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPAdd)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPAddFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompare)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareEQ)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareEQFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareGE)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareGEFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareGT)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareGTFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareLE)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareLEFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareLT)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareLTFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPDiv)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMax)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMaxFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMaxNum)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMaxNumFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMin)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMinFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMinNum)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMinNumFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMul)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMulFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMulAdd)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMulAddFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMulSub)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMulSubFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMulX)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPNegMulAdd)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPNegMulSub)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRecipEstimate)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRecipEstimateFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRecipStep))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRecipStepFused)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRecpX)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRSqrtEstimate)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRSqrtEstimateFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRSqrtStep))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRSqrtStepFused)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPSqrt)));
+ SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPSub)));
+
+ SetDelegateInfo(typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert)));
+
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPAdd)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPAddFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompare)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareEQ)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareEQFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareGE)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareGEFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareGT)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareGTFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareLE)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareLEFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareLT)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareLTFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPDiv)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMax)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMaxFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMaxNum)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMaxNumFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMin)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMinFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMinNum)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMinNumFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMul)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMulFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMulAdd)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMulAddFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMulSub)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMulSubFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMulX)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPNegMulAdd)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPNegMulSub)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRecipEstimate)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRecipEstimateFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRecipStep))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRecipStepFused)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRecpX)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRSqrtEstimate)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRSqrtEstimateFpscr))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRSqrtStep))); // A32 only.
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRSqrtStepFused)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPSqrt)));
+ SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPSub)));
+ }
+ }
+}
diff --git a/ARMeilleure/Translation/DirectCallStubs.cs b/ARMeilleure/Translation/DirectCallStubs.cs
index 42a78c71..7c11fdb2 100644
--- a/ARMeilleure/Translation/DirectCallStubs.cs
+++ b/ARMeilleure/Translation/DirectCallStubs.cs
@@ -2,6 +2,7 @@
using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.State;
using System;
+using System.Diagnostics;
using System.Runtime.InteropServices;
using static ARMeilleure.IntermediateRepresentation.OperandHelper;
@@ -12,10 +13,10 @@ namespace ARMeilleure.Translation
{
private delegate long GuestFunction(IntPtr nativeContextPtr);
- private static GuestFunction _directCallStub;
- private static GuestFunction _directTailCallStub;
- private static GuestFunction _indirectCallStub;
- private static GuestFunction _indirectTailCallStub;
+ private static IntPtr _directCallStubPtr;
+ private static IntPtr _directTailCallStubPtr;
+ private static IntPtr _indirectCallStubPtr;
+ private static IntPtr _indirectTailCallStubPtr;
private static readonly object _lock = new object();
private static bool _initialized;
@@ -23,25 +24,32 @@ namespace ARMeilleure.Translation
public static void InitializeStubs()
{
if (_initialized) return;
+
lock (_lock)
{
if (_initialized) return;
- _directCallStub = GenerateDirectCallStub(false);
- _directTailCallStub = GenerateDirectCallStub(true);
- _indirectCallStub = GenerateIndirectCallStub(false);
- _indirectTailCallStub = GenerateIndirectCallStub(true);
+
+ _directCallStubPtr = Marshal.GetFunctionPointerForDelegate<GuestFunction>(GenerateDirectCallStub(false));
+ _directTailCallStubPtr = Marshal.GetFunctionPointerForDelegate<GuestFunction>(GenerateDirectCallStub(true));
+ _indirectCallStubPtr = Marshal.GetFunctionPointerForDelegate<GuestFunction>(GenerateIndirectCallStub(false));
+ _indirectTailCallStubPtr = Marshal.GetFunctionPointerForDelegate<GuestFunction>(GenerateIndirectCallStub(true));
+
_initialized = true;
}
}
public static IntPtr DirectCallStub(bool tailCall)
{
- return Marshal.GetFunctionPointerForDelegate(tailCall ? _directTailCallStub : _directCallStub);
+ Debug.Assert(_initialized);
+
+ return tailCall ? _directTailCallStubPtr : _directCallStubPtr;
}
public static IntPtr IndirectCallStub(bool tailCall)
{
- return Marshal.GetFunctionPointerForDelegate(tailCall ? _indirectTailCallStub : _indirectCallStub);
+ Debug.Assert(_initialized);
+
+ return tailCall ? _indirectTailCallStubPtr : _indirectCallStubPtr;
}
private static void EmitCall(EmitterContext context, Operand address, bool tailCall)
@@ -70,21 +78,18 @@ namespace ARMeilleure.Translation
Operand address = context.Load(OperandType.I64, context.Add(nativeContextPtr, Const((long)NativeContext.GetCallAddressOffset())));
address = context.BitwiseOr(address, Const(address.Type, 1)); // Set call flag.
- Operand functionAddr = context.Call(new _U64_U64(NativeInterface.GetFunctionAddress), address);
+ Operand functionAddr = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)), address);
EmitCall(context, functionAddr, tailCall);
ControlFlowGraph cfg = context.GetControlFlowGraph();
- OperandType[] argTypes = new OperandType[]
- {
- OperandType.I64
- };
+ OperandType[] argTypes = new OperandType[] { OperandType.I64 };
return Compiler.Compile<GuestFunction>(cfg, argTypes, OperandType.I64, CompilerOptions.HighCq);
}
/// <summary>
- /// Generates a stub that is used to find function addresses and add them to an indirect table.
+ /// Generates a stub that is used to find function addresses and add them to an indirect table.
/// Used for indirect calls entries (already claimed) when their jump table does not have the host address yet.
/// Takes a NativeContext like a translated guest function, and extracts the target indirect table entry from the NativeContext.
/// If the function we find is highCq, the entry in the table is updated to point to that function rather than this stub.
@@ -100,17 +105,14 @@ namespace ARMeilleure.Translation
// We need to find the missing function. If the function is HighCq, then it replaces this stub in the indirect table.
// Either way, we call it afterwards.
- Operand functionAddr = context.Call(new _U64_U64_U64(NativeInterface.GetIndirectFunctionAddress), address, entryAddress);
+ Operand functionAddr = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetIndirectFunctionAddress)), address, entryAddress);
// Call and save the function.
EmitCall(context, functionAddr, tailCall);
ControlFlowGraph cfg = context.GetControlFlowGraph();
- OperandType[] argTypes = new OperandType[]
- {
- OperandType.I64
- };
+ OperandType[] argTypes = new OperandType[] { OperandType.I64 };
return Compiler.Compile<GuestFunction>(cfg, argTypes, OperandType.I64, CompilerOptions.HighCq);
}
diff --git a/ARMeilleure/Translation/EmitterContext.cs b/ARMeilleure/Translation/EmitterContext.cs
index f14c920e..656f1704 100644
--- a/ARMeilleure/Translation/EmitterContext.cs
+++ b/ARMeilleure/Translation/EmitterContext.cs
@@ -3,12 +3,14 @@ using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.State;
using System;
using System.Collections.Generic;
-using System.Runtime.InteropServices;
+using System.Reflection;
using static ARMeilleure.IntermediateRepresentation.OperandHelper;
namespace ARMeilleure.Translation
{
+ using PTC;
+
class EmitterContext
{
private Dictionary<Operand, BasicBlock> _irLabels;
@@ -79,42 +81,52 @@ namespace ARMeilleure.Translation
return Add(Instruction.ByteSwap, Local(op1.Type), op1);
}
- public Operand Call(Delegate func, params Operand[] callArgs)
+ public Operand Call(MethodInfo info, params Operand[] callArgs)
{
- // Add the delegate to the cache to ensure it will not be garbage collected.
- func = DelegateCache.GetOrAdd(func);
+ if (Ptc.State == PtcState.Disabled)
+ {
+ IntPtr funcPtr = Delegates.GetDelegateFuncPtr(info);
- IntPtr ptr = Marshal.GetFunctionPointerForDelegate<Delegate>(func);
+ OperandType returnType = GetOperandType(info.ReturnType);
- Symbols.Add((ulong)ptr.ToInt64(), func.Method.Name);
+ Symbols.Add((ulong)funcPtr.ToInt64(), info.Name);
- OperandType returnType = GetOperandType(func.Method.ReturnType);
+ return Call(Const(funcPtr.ToInt64()), returnType, callArgs);
+ }
+ else
+ {
+ int index = Delegates.GetDelegateIndex(info);
- return Call(Const(ptr.ToInt64()), returnType, callArgs);
- }
+ IntPtr funcPtr = Delegates.GetDelegateFuncPtrByIndex(index);
- private static Dictionary<TypeCode, OperandType> _typeCodeToOperandTypeMap =
- new Dictionary<TypeCode, OperandType>()
- {
- { TypeCode.Boolean, OperandType.I32 },
- { TypeCode.Byte, OperandType.I32 },
- { TypeCode.Char, OperandType.I32 },
- { TypeCode.Double, OperandType.FP64 },
- { TypeCode.Int16, OperandType.I32 },
- { TypeCode.Int32, OperandType.I32 },
- { TypeCode.Int64, OperandType.I64 },
- { TypeCode.SByte, OperandType.I32 },
- { TypeCode.Single, OperandType.FP32 },
- { TypeCode.UInt16, OperandType.I32 },
- { TypeCode.UInt32, OperandType.I32 },
- { TypeCode.UInt64, OperandType.I64 }
- };
+ OperandType returnType = GetOperandType(info.ReturnType);
+
+ Symbols.Add((ulong)funcPtr.ToInt64(), info.Name);
+
+ return Call(Const(funcPtr.ToInt64(), true, index), returnType, callArgs);
+ }
+ }
private static OperandType GetOperandType(Type type)
{
- if (_typeCodeToOperandTypeMap.TryGetValue(Type.GetTypeCode(type), out OperandType ot))
+ if (type == typeof(bool) || type == typeof(byte) ||
+ type == typeof(char) || type == typeof(short) ||
+ type == typeof(int) || type == typeof(sbyte) ||
+ type == typeof(ushort) || type == typeof(uint))
{
- return ot;
+ return OperandType.I32;
+ }
+ else if (type == typeof(long) || type == typeof(ulong))
+ {
+ return OperandType.I64;
+ }
+ else if (type == typeof(double))
+ {
+ return OperandType.FP64;
+ }
+ else if (type == typeof(float))
+ {
+ return OperandType.FP32;
}
else if (type == typeof(V128))
{
@@ -124,8 +136,10 @@ namespace ARMeilleure.Translation
{
return OperandType.None;
}
-
- throw new ArgumentException($"Invalid type \"{type.Name}\".");
+ else
+ {
+ throw new ArgumentException($"Invalid type \"{type.Name}\".");
+ }
}
public Operand Call(Operand address, OperandType returnType, params Operand[] callArgs)
@@ -615,4 +629,4 @@ namespace ARMeilleure.Translation
return new ControlFlowGraph(_irBlocks.First, _irBlocks);
}
}
-} \ No newline at end of file
+}
diff --git a/ARMeilleure/Translation/JitCache.cs b/ARMeilleure/Translation/JitCache.cs
index 32d40c20..a828b4ed 100644
--- a/ARMeilleure/Translation/JitCache.cs
+++ b/ARMeilleure/Translation/JitCache.cs
@@ -12,11 +12,12 @@ namespace ARMeilleure.Translation
private const int PageSize = 4 * 1024;
private const int PageMask = PageSize - 1;
- private const int CodeAlignment = 4; // Bytes
+ private const int CodeAlignment = 4; // Bytes.
private const int CacheSize = 2047 * 1024 * 1024;
private static ReservedRegion _jitRegion;
private static int _offset;
+
private static readonly List<JitCacheEntry> _cacheEntries = new List<JitCacheEntry>();
private static readonly object _lock = new object();
@@ -25,19 +26,23 @@ namespace ARMeilleure.Translation
public static void Initialize(IJitMemoryAllocator allocator)
{
if (_initialized) return;
+
lock (_lock)
{
if (_initialized) return;
+
_jitRegion = new ReservedRegion(allocator, CacheSize);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- _jitRegion.ExpandIfNeeded(PageSize);
+ _jitRegion.ExpandIfNeeded((ulong)PageSize);
+
JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize);
// The first page is used for the table based SEH structs.
_offset = PageSize;
}
+
_initialized = true;
}
}
@@ -97,13 +102,13 @@ namespace ARMeilleure.Translation
_offset += codeSize;
- _jitRegion.ExpandIfNeeded((ulong)_offset);
-
- if ((ulong)(uint)_offset > CacheSize)
+ if (_offset > CacheSize)
{
- throw new OutOfMemoryException();
+ throw new OutOfMemoryException("JIT Cache exhausted.");
}
+ _jitRegion.ExpandIfNeeded((ulong)_offset);
+
return allocOffset;
}
diff --git a/ARMeilleure/Translation/JitUnwindWindows.cs b/ARMeilleure/Translation/JitUnwindWindows.cs
index 3f5b3282..e118d129 100644
--- a/ARMeilleure/Translation/JitUnwindWindows.cs
+++ b/ARMeilleure/Translation/JitUnwindWindows.cs
@@ -27,7 +27,7 @@ namespace ARMeilleure.Translation
public unsafe fixed ushort UnwindCodes[MaxUnwindCodesArraySize];
}
- private enum UnwindOperation
+ private enum UnwindOp
{
PushNonvol = 0,
AllocLarge = 1,
@@ -117,12 +117,12 @@ namespace ARMeilleure.Translation
if (stackOffset <= 0xFFFF0)
{
- _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOperation.SaveXmm128, entry.PrologOffset, entry.RegIndex);
+ _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128, entry.PrologOffset, entry.RegIndex);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset / 16);
}
else
{
- _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOperation.SaveXmm128Far, entry.PrologOffset, entry.RegIndex);
+ _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128Far, entry.PrologOffset, entry.RegIndex);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 0);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 16);
}
@@ -138,16 +138,16 @@ namespace ARMeilleure.Translation
if (allocSize <= 128)
{
- _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOperation.AllocSmall, entry.PrologOffset, (allocSize / 8) - 1);
+ _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocSmall, entry.PrologOffset, (allocSize / 8) - 1);
}
else if (allocSize <= 0x7FFF8)
{
- _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOperation.AllocLarge, entry.PrologOffset, 0);
+ _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 0);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize / 8);
}
else
{
- _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOperation.AllocLarge, entry.PrologOffset, 1);
+ _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 1);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 0);
_unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 16);
}
@@ -157,7 +157,7 @@ namespace ARMeilleure.Translation
case UnwindPseudoOp.PushReg:
{
- _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOperation.PushNonvol, entry.PrologOffset, entry.RegIndex);
+ _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.PushNonvol, entry.PrologOffset, entry.RegIndex);
break;
}
@@ -180,7 +180,7 @@ namespace ARMeilleure.Translation
return _runtimeFunction;
}
- private static ushort PackUnwindOp(UnwindOperation op, int prologOffset, int opInfo)
+ private static ushort PackUnwindOp(UnwindOp op, int prologOffset, int opInfo)
{
return (ushort)(prologOffset | ((int)op << 8) | (opInfo << 12));
}
diff --git a/ARMeilleure/Translation/JumpTable.cs b/ARMeilleure/Translation/JumpTable.cs
index 40ea0fce..fe7a6ec3 100644
--- a/ARMeilleure/Translation/JumpTable.cs
+++ b/ARMeilleure/Translation/JumpTable.cs
@@ -3,11 +3,14 @@ using ARMeilleure.Memory;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
namespace ARMeilleure.Translation
{
+ using PTC;
+
class JumpTable
{
// The jump table is a block of (guestAddress, hostAddress) function mappings.
@@ -15,10 +18,9 @@ namespace ARMeilleure.Translation
// reserved specifically for each call.
// The _dependants dictionary can be used to update the hostAddress for any functions that change.
- public const int JumpTableStride = 16; // 8 byte guest address, 8 byte host address
+ public const int JumpTableStride = 16; // 8 byte guest address, 8 byte host address.
private const int JumpTableSize = 1048576;
-
private const int JumpTableByteSize = JumpTableSize * JumpTableStride;
// The dynamic table is also a block of (guestAddress, hostAddress) function mappings.
@@ -32,74 +34,125 @@ namespace ARMeilleure.Translation
// If it is 0, NativeInterface is called to find the rejited address of the call.
// If none is found, the hostAddress entry stays at 0. Otherwise, the new address is placed in the entry.
- // If the table size is exhausted and we didn't find our desired address, we fall back to requesting
+ // If the table size is exhausted and we didn't find our desired address, we fall back to requesting
// the function from the JIT.
- private const int DynamicTableSize = 1048576;
-
public const int DynamicTableElems = 1;
public const int DynamicTableStride = DynamicTableElems * JumpTableStride;
- private const int DynamicTableByteSize = DynamicTableSize * JumpTableStride * DynamicTableElems;
+ private const int DynamicTableSize = 1048576;
+ private const int DynamicTableByteSize = DynamicTableSize * DynamicTableStride;
- private int _tableEnd = 0;
- private int _dynTableEnd = 0;
+ private readonly ReservedRegion _jumpRegion;
+ private readonly ReservedRegion _dynamicRegion;
- private ConcurrentDictionary<ulong, TranslatedFunction> _targets;
- private ConcurrentDictionary<ulong, LinkedList<int>> _dependants; // TODO: Attach to TranslatedFunction or a wrapper class.
+ private int _tableEnd = 0;
+ private int _dynTableEnd = 0;
- private ReservedRegion _jumpRegion;
- private ReservedRegion _dynamicRegion;
- public IntPtr JumpPointer => _jumpRegion.Pointer;
+ public IntPtr JumpPointer => _jumpRegion.Pointer;
public IntPtr DynamicPointer => _dynamicRegion.Pointer;
+ public int TableEnd => _tableEnd;
+ public int DynTableEnd => _dynTableEnd;
+
+ public ConcurrentDictionary<ulong, TranslatedFunction> Targets { get; }
+ public ConcurrentDictionary<ulong, LinkedList<int>> Dependants { get; } // TODO: Attach to TranslatedFunction or a wrapper class.
+
public JumpTable(IJitMemoryAllocator allocator)
{
- _jumpRegion = new ReservedRegion(allocator, JumpTableByteSize);
+ _jumpRegion = new ReservedRegion(allocator, JumpTableByteSize);
_dynamicRegion = new ReservedRegion(allocator, DynamicTableByteSize);
- _targets = new ConcurrentDictionary<ulong, TranslatedFunction>();
- _dependants = new ConcurrentDictionary<ulong, LinkedList<int>>();
+ Targets = new ConcurrentDictionary<ulong, TranslatedFunction>();
+ Dependants = new ConcurrentDictionary<ulong, LinkedList<int>>();
Symbols.Add((ulong)_jumpRegion.Pointer.ToInt64(), JumpTableByteSize, JumpTableStride, "JMP_TABLE");
Symbols.Add((ulong)_dynamicRegion.Pointer.ToInt64(), DynamicTableByteSize, DynamicTableStride, "DYN_TABLE");
}
+ public void Initialize(PtcJumpTable ptcJumpTable, ConcurrentDictionary<ulong, TranslatedFunction> funcs)
+ {
+ _tableEnd = ptcJumpTable.TableEnd;
+ _dynTableEnd = ptcJumpTable.DynTableEnd;
+
+ foreach (ulong guestAddress in ptcJumpTable.Targets)
+ {
+ if (funcs.TryGetValue(guestAddress, out TranslatedFunction func))
+ {
+ Targets.TryAdd(guestAddress, func);
+ }
+ else
+ {
+ throw new KeyNotFoundException($"({nameof(guestAddress)} = 0x{guestAddress:X16})");
+ }
+ }
+
+ foreach (var item in ptcJumpTable.Dependants)
+ {
+ Dependants.TryAdd(item.Key, new LinkedList<int>(item.Value));
+ }
+ }
+
public void RegisterFunction(ulong address, TranslatedFunction func)
{
address &= ~3UL;
- _targets.AddOrUpdate(address, func, (key, oldFunc) => func);
- long funcPtr = func.GetPointer().ToInt64();
+ Targets.AddOrUpdate(address, func, (key, oldFunc) => func);
+ long funcPtr = func.FuncPtr.ToInt64();
// Update all jump table entries that target this address.
- if (_dependants.TryGetValue(address, out LinkedList<int> myDependants))
+ if (Dependants.TryGetValue(address, out LinkedList<int> myDependants))
{
lock (myDependants)
{
- foreach (var entry in myDependants)
+ foreach (int entry in myDependants)
{
- IntPtr addr = _jumpRegion.Pointer + entry * JumpTableStride;
+ IntPtr addr = GetEntryAddressJumpTable(entry);
+
Marshal.WriteInt64(addr, 8, funcPtr);
}
}
}
}
- public int ReserveDynamicEntry(bool isJump)
+ public int ReserveTableEntry(long ownerAddress, long address, bool isJump)
{
- int entry = Interlocked.Increment(ref _dynTableEnd);
- if (entry >= DynamicTableSize)
+ int entry = Interlocked.Increment(ref _tableEnd);
+
+ ExpandIfNeededJumpTable(entry);
+
+ // Is the address we have already registered? If so, put the function address in the jump table.
+ // If not, it will point to the direct call stub.
+ long value = DirectCallStubs.DirectCallStub(isJump).ToInt64();
+ if (Targets.TryGetValue((ulong)address, out TranslatedFunction func))
{
- throw new OutOfMemoryException("JIT Dynamic Jump Table exhausted.");
+ value = func.FuncPtr.ToInt64();
+ }
+
+ // Make sure changes to the function at the target address update this jump table entry.
+ LinkedList<int> targetDependants = Dependants.GetOrAdd((ulong)address, (addr) => new LinkedList<int>());
+ lock (targetDependants)
+ {
+ targetDependants.AddLast(entry);
}
- _dynamicRegion.ExpandIfNeeded((ulong)((entry + 1) * DynamicTableStride));
+ IntPtr addr = GetEntryAddressJumpTable(entry);
- // Initialize all host function pointers to the indirect call stub.
+ Marshal.WriteInt64(addr, 0, address);
+ Marshal.WriteInt64(addr, 8, value);
+
+ return entry;
+ }
+
+ public int ReserveDynamicEntry(bool isJump)
+ {
+ int entry = Interlocked.Increment(ref _dynTableEnd);
- IntPtr addr = _dynamicRegion.Pointer + entry * DynamicTableStride;
- long stubPtr = (long)DirectCallStubs.IndirectCallStub(isJump);
+ ExpandIfNeededDynamicTable(entry);
+
+ // Initialize all host function pointers to the indirect call stub.
+ IntPtr addr = GetEntryAddressDynamicTable(entry);
+ long stubPtr = DirectCallStubs.IndirectCallStub(isJump).ToInt64();
for (int i = 0; i < DynamicTableElems; i++)
{
@@ -109,37 +162,46 @@ namespace ARMeilleure.Translation
return entry;
}
- public int ReserveTableEntry(long ownerAddress, long address, bool isJump)
+ public void ExpandIfNeededJumpTable(int entries)
{
- int entry = Interlocked.Increment(ref _tableEnd);
- if (entry >= JumpTableSize)
+ Debug.Assert(entries > 0);
+
+ if (entries < JumpTableSize)
+ {
+ _jumpRegion.ExpandIfNeeded((ulong)((entries + 1) * JumpTableStride));
+ }
+ else
{
throw new OutOfMemoryException("JIT Direct Jump Table exhausted.");
}
+ }
- _jumpRegion.ExpandIfNeeded((ulong)((entry + 1) * JumpTableStride));
+ public void ExpandIfNeededDynamicTable(int entries)
+ {
+ Debug.Assert(entries > 0);
- // Is the address we have already registered? If so, put the function address in the jump table.
- // If not, it will point to the direct call stub.
- long value = (long)DirectCallStubs.DirectCallStub(isJump);
- if (_targets.TryGetValue((ulong)address, out TranslatedFunction func))
+ if (entries < DynamicTableSize)
{
- value = func.GetPointer().ToInt64();
+ _dynamicRegion.ExpandIfNeeded((ulong)((entries + 1) * DynamicTableStride));
}
-
- // Make sure changes to the function at the target address update this jump table entry.
- LinkedList<int> targetDependants = _dependants.GetOrAdd((ulong)address, (addr) => new LinkedList<int>());
- lock (targetDependants)
+ else
{
- targetDependants.AddLast(entry);
+ throw new OutOfMemoryException("JIT Dynamic Jump Table exhausted.");
}
+ }
- IntPtr addr = _jumpRegion.Pointer + entry * JumpTableStride;
+ public IntPtr GetEntryAddressJumpTable(int entry)
+ {
+ Debug.Assert(entry >= 1 && entry <= _tableEnd);
- Marshal.WriteInt64(addr, 0, address);
- Marshal.WriteInt64(addr, 8, value);
+ return _jumpRegion.Pointer + entry * JumpTableStride;
+ }
- return entry;
+ public IntPtr GetEntryAddressDynamicTable(int entry)
+ {
+ Debug.Assert(entry >= 1 && entry <= _dynTableEnd);
+
+ return _dynamicRegion.Pointer + entry * DynamicTableStride;
}
}
}
diff --git a/ARMeilleure/Translation/PTC/EncodingCache.cs b/ARMeilleure/Translation/PTC/EncodingCache.cs
new file mode 100644
index 00000000..b87e0d7a
--- /dev/null
+++ b/ARMeilleure/Translation/PTC/EncodingCache.cs
@@ -0,0 +1,9 @@
+using System.Text;
+
+namespace ARMeilleure.Translation.PTC
+{
+ internal static class EncodingCache
+ {
+ internal static readonly Encoding UTF8NoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
+ }
+} \ No newline at end of file
diff --git a/ARMeilleure/Translation/PTC/Ptc.cs b/ARMeilleure/Translation/PTC/Ptc.cs
new file mode 100644
index 00000000..76d3d7e1
--- /dev/null
+++ b/ARMeilleure/Translation/PTC/Ptc.cs
@@ -0,0 +1,768 @@
+using ARMeilleure.CodeGen;
+using ARMeilleure.CodeGen.Unwinding;
+using ARMeilleure.Memory;
+using Ryujinx.Common.Logging;
+using System;
+using System.Buffers.Binary;
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.IO;
+using System.IO.Compression;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics.X86;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ARMeilleure.Translation.PTC
+{
+ public static class Ptc
+ {
+ private const string HeaderMagic = "PTChd";
+
+ private const int InternalVersion = 0; //! To be incremented manually for each change to the ARMeilleure project.
+
+ private const string BaseDir = "Ryujinx";
+
+ private const string ActualDir = "0";
+ private const string BackupDir = "1";
+
+ private const string TitleIdTextDefault = "0000000000000000";
+ private const string DisplayVersionDefault = "0";
+
+ internal const int PageTablePointerIndex = -1; // Must be a negative value.
+ internal const int JumpPointerIndex = -2; // Must be a negative value.
+ internal const int DynamicPointerIndex = -3; // Must be a negative value.
+
+ private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
+
+ private static readonly MemoryStream _infosStream;
+ private static readonly MemoryStream _codesStream;
+ private static readonly MemoryStream _relocsStream;
+ private static readonly MemoryStream _unwindInfosStream;
+
+ private static readonly BinaryWriter _infosWriter;
+
+ private static readonly BinaryFormatter _binaryFormatter;
+
+ private static readonly ManualResetEvent _waitEvent;
+
+ private static readonly AutoResetEvent _loggerEvent;
+
+ private static readonly string _basePath;
+
+ private static readonly object _lock;
+
+ private static bool _disposed;
+
+ private static volatile int _translateCount;
+ private static volatile int _rejitCount;
+
+ internal static PtcJumpTable PtcJumpTable { get; private set; }
+
+ internal static string TitleIdText { get; private set; }
+ internal static string DisplayVersion { get; private set; }
+
+ internal static string CachePathActual { get; private set; }
+ internal static string CachePathBackup { get; private set; }
+
+ internal static PtcState State { get; private set; }
+
+ static Ptc()
+ {
+ _infosStream = new MemoryStream();
+ _codesStream = new MemoryStream();
+ _relocsStream = new MemoryStream();
+ _unwindInfosStream = new MemoryStream();
+
+ _infosWriter = new BinaryWriter(_infosStream, EncodingCache.UTF8NoBOM, true);
+
+ _binaryFormatter = new BinaryFormatter();
+
+ _waitEvent = new ManualResetEvent(true);
+
+ _loggerEvent = new AutoResetEvent(false);
+
+ _basePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), BaseDir);
+
+ _lock = new object();
+
+ _disposed = false;
+
+ PtcJumpTable = new PtcJumpTable();
+
+ TitleIdText = TitleIdTextDefault;
+ DisplayVersion = DisplayVersionDefault;
+
+ CachePathActual = string.Empty;
+ CachePathBackup = string.Empty;
+
+ Disable();
+
+ AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
+ AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit;
+ }
+
+ public static void Initialize(string titleIdText, string displayVersion, bool enabled)
+ {
+ Wait();
+ ClearMemoryStreams();
+ PtcJumpTable.Clear();
+
+ PtcProfiler.Stop();
+ PtcProfiler.Wait();
+ PtcProfiler.ClearEntries();
+
+ if (String.IsNullOrEmpty(titleIdText) || titleIdText == TitleIdTextDefault)
+ {
+ TitleIdText = TitleIdTextDefault;
+ DisplayVersion = DisplayVersionDefault;
+
+ CachePathActual = string.Empty;
+ CachePathBackup = string.Empty;
+
+ Disable();
+
+ return;
+ }
+
+ Logger.PrintInfo(LogClass.Ptc, $"Initializing Profiled Persistent Translation Cache (enabled: {enabled}).");
+
+ TitleIdText = titleIdText;
+ DisplayVersion = !String.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault;
+
+ if (enabled)
+ {
+ string workPathActual = Path.Combine(_basePath, "games", TitleIdText, "cache", "cpu", ActualDir);
+ string workPathBackup = Path.Combine(_basePath, "games", TitleIdText, "cache", "cpu", BackupDir);
+
+ if (!Directory.Exists(workPathActual))
+ {
+ Directory.CreateDirectory(workPathActual);
+ }
+
+ if (!Directory.Exists(workPathBackup))
+ {
+ Directory.CreateDirectory(workPathBackup);
+ }
+
+ CachePathActual = Path.Combine(workPathActual, DisplayVersion);
+ CachePathBackup = Path.Combine(workPathBackup, DisplayVersion);
+
+ Enable();
+
+ PreLoad();
+ PtcProfiler.PreLoad();
+ }
+ else
+ {
+ CachePathActual = string.Empty;
+ CachePathBackup = string.Empty;
+
+ Disable();
+ }
+ }
+
+ internal static void ClearMemoryStreams()
+ {
+ _infosStream.SetLength(0L);
+ _codesStream.SetLength(0L);
+ _relocsStream.SetLength(0L);
+ _unwindInfosStream.SetLength(0L);
+ }
+
+ private static void PreLoad()
+ {
+ string fileNameActual = String.Concat(CachePathActual, ".cache");
+ string fileNameBackup = String.Concat(CachePathBackup, ".cache");
+
+ FileInfo fileInfoActual = new FileInfo(fileNameActual);
+ FileInfo fileInfoBackup = new FileInfo(fileNameBackup);
+
+ if (fileInfoActual.Exists && fileInfoActual.Length != 0L)
+ {
+ if (!Load(fileNameActual))
+ {
+ if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
+ {
+ Load(fileNameBackup);
+ }
+ }
+ }
+ else if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
+ {
+ Load(fileNameBackup);
+ }
+ }
+
+ private static bool Load(string fileName)
+ {
+ using (FileStream compressedStream = new FileStream(fileName, FileMode.Open))
+ using (DeflateStream deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress, true))
+ using (MemoryStream stream = new MemoryStream())
+ using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
+ {
+ int hashSize = md5.HashSize / 8;
+
+ deflateStream.CopyTo(stream);
+
+ stream.Seek(0L, SeekOrigin.Begin);
+
+ byte[] currentHash = new byte[hashSize];
+ stream.Read(currentHash, 0, hashSize);
+
+ byte[] expectedHash = md5.ComputeHash(stream);
+
+ if (!CompareHash(currentHash, expectedHash))
+ {
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ stream.Seek((long)hashSize, SeekOrigin.Begin);
+
+ Header header = ReadHeader(stream);
+
+ if (header.Magic != HeaderMagic)
+ {
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ if (header.CacheFileVersion != InternalVersion)
+ {
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ if (header.FeatureInfo != GetFeatureInfo())
+ {
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ if (header.InfosLen % InfoEntry.Stride != 0)
+ {
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ byte[] infosBuf = new byte[header.InfosLen];
+ byte[] codesBuf = new byte[header.CodesLen];
+ byte[] relocsBuf = new byte[header.RelocsLen];
+ byte[] unwindInfosBuf = new byte[header.UnwindInfosLen];
+
+ stream.Read(infosBuf, 0, header.InfosLen);
+ stream.Read(codesBuf, 0, header.CodesLen);
+ stream.Read(relocsBuf, 0, header.RelocsLen);
+ stream.Read(unwindInfosBuf, 0, header.UnwindInfosLen);
+
+ try
+ {
+ PtcJumpTable = (PtcJumpTable)_binaryFormatter.Deserialize(stream);
+ }
+ catch
+ {
+ PtcJumpTable = new PtcJumpTable();
+
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ _infosStream.Write(infosBuf, 0, header.InfosLen);
+ _codesStream.Write(codesBuf, 0, header.CodesLen);
+ _relocsStream.Write(relocsBuf, 0, header.RelocsLen);
+ _unwindInfosStream.Write(unwindInfosBuf, 0, header.UnwindInfosLen);
+
+ return true;
+ }
+ }
+
+ private static bool CompareHash(ReadOnlySpan<byte> currentHash, ReadOnlySpan<byte> expectedHash)
+ {
+ return currentHash.SequenceEqual(expectedHash);
+ }
+
+ private static Header ReadHeader(MemoryStream stream)
+ {
+ using (BinaryReader headerReader = new BinaryReader(stream, EncodingCache.UTF8NoBOM, true))
+ {
+ Header header = new Header();
+
+ header.Magic = headerReader.ReadString();
+
+ header.CacheFileVersion = headerReader.ReadInt32();
+ header.FeatureInfo = headerReader.ReadUInt64();
+
+ header.InfosLen = headerReader.ReadInt32();
+ header.CodesLen = headerReader.ReadInt32();
+ header.RelocsLen = headerReader.ReadInt32();
+ header.UnwindInfosLen = headerReader.ReadInt32();
+
+ return header;
+ }
+ }
+
+ private static void InvalidateCompressedStream(FileStream compressedStream)
+ {
+ compressedStream.SetLength(0L);
+ }
+
+ private static void PreSave(object state)
+ {
+ _waitEvent.Reset();
+
+ string fileNameActual = String.Concat(CachePathActual, ".cache");
+ string fileNameBackup = String.Concat(CachePathBackup, ".cache");
+
+ FileInfo fileInfoActual = new FileInfo(fileNameActual);
+
+ if (fileInfoActual.Exists && fileInfoActual.Length != 0L)
+ {
+ File.Copy(fileNameActual, fileNameBackup, true);
+ }
+
+ Save(fileNameActual);
+
+ _waitEvent.Set();
+ }
+
+ private static void Save(string fileName)
+ {
+ using (MemoryStream stream = new MemoryStream())
+ using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
+ {
+ int hashSize = md5.HashSize / 8;
+
+ stream.Seek((long)hashSize, SeekOrigin.Begin);
+
+ WriteHeader(stream);
+
+ _infosStream.WriteTo(stream);
+ _codesStream.WriteTo(stream);
+ _relocsStream.WriteTo(stream);
+ _unwindInfosStream.WriteTo(stream);
+
+ _binaryFormatter.Serialize(stream, PtcJumpTable);
+
+ stream.Seek((long)hashSize, SeekOrigin.Begin);
+ byte[] hash = md5.ComputeHash(stream);
+
+ stream.Seek(0L, SeekOrigin.Begin);
+ stream.Write(hash, 0, hashSize);
+
+ using (FileStream compressedStream = new FileStream(fileName, FileMode.OpenOrCreate))
+ using (DeflateStream deflateStream = new DeflateStream(compressedStream, SaveCompressionLevel, true))
+ {
+ try
+ {
+ stream.WriteTo(deflateStream);
+ }
+ catch
+ {
+ compressedStream.Position = 0L;
+ }
+
+ if (compressedStream.Position < compressedStream.Length)
+ {
+ compressedStream.SetLength(compressedStream.Position);
+ }
+ }
+ }
+ }
+
+ private static void WriteHeader(MemoryStream stream)
+ {
+ using (BinaryWriter headerWriter = new BinaryWriter(stream, EncodingCache.UTF8NoBOM, true))
+ {
+ headerWriter.Write((string)HeaderMagic); // Header.Magic
+
+ headerWriter.Write((int)InternalVersion); // Header.CacheFileVersion
+ headerWriter.Write((ulong)GetFeatureInfo()); // Header.FeatureInfo
+
+ headerWriter.Write((int)_infosStream.Length); // Header.InfosLen
+ headerWriter.Write((int)_codesStream.Length); // Header.CodesLen
+ headerWriter.Write((int)_relocsStream.Length); // Header.RelocsLen
+ headerWriter.Write((int)_unwindInfosStream.Length); // Header.UnwindInfosLen
+ }
+ }
+
+ internal static void LoadTranslations(ConcurrentDictionary<ulong, TranslatedFunction> funcs, IntPtr pageTablePointer, JumpTable jumpTable)
+ {
+ if ((int)_infosStream.Length == 0 ||
+ (int)_codesStream.Length == 0 ||
+ (int)_relocsStream.Length == 0 ||
+ (int)_unwindInfosStream.Length == 0)
+ {
+ return;
+ }
+
+ Debug.Assert(funcs.Count == 0);
+
+ _infosStream.Seek(0L, SeekOrigin.Begin);
+ _codesStream.Seek(0L, SeekOrigin.Begin);
+ _relocsStream.Seek(0L, SeekOrigin.Begin);
+ _unwindInfosStream.Seek(0L, SeekOrigin.Begin);
+
+ using (BinaryReader infosReader = new BinaryReader(_infosStream, EncodingCache.UTF8NoBOM, true))
+ using (BinaryReader codesReader = new BinaryReader(_codesStream, EncodingCache.UTF8NoBOM, true))
+ using (BinaryReader relocsReader = new BinaryReader(_relocsStream, EncodingCache.UTF8NoBOM, true))
+ using (BinaryReader unwindInfosReader = new BinaryReader(_unwindInfosStream, EncodingCache.UTF8NoBOM, true))
+ {
+ int infosEntriesCount = (int)_infosStream.Length / InfoEntry.Stride;
+
+ for (int i = 0; i < infosEntriesCount; i++)
+ {
+ InfoEntry infoEntry = ReadInfo(infosReader);
+
+ byte[] code = ReadCode(codesReader, infoEntry.CodeLen);
+
+ if (infoEntry.RelocEntriesCount != 0)
+ {
+ RelocEntry[] relocEntries = GetRelocEntries(relocsReader, infoEntry.RelocEntriesCount);
+
+ PatchCode(code, relocEntries, pageTablePointer, jumpTable);
+ }
+
+ UnwindInfo unwindInfo = ReadUnwindInfo(unwindInfosReader);
+
+ TranslatedFunction func = FastTranslate(code, unwindInfo, infoEntry.HighCq);
+
+ funcs.AddOrUpdate((ulong)infoEntry.Address, func, (key, oldFunc) => func.HighCq && !oldFunc.HighCq ? func : oldFunc);
+ }
+ }
+
+ if (_infosStream.Position < _infosStream.Length ||
+ _codesStream.Position < _codesStream.Length ||
+ _relocsStream.Position < _relocsStream.Length ||
+ _unwindInfosStream.Position < _unwindInfosStream.Length)
+ {
+ throw new Exception("Could not reach the end of one or more memory streams.");
+ }
+
+ jumpTable.Initialize(PtcJumpTable, funcs);
+
+ PtcJumpTable.WriteJumpTable(jumpTable, funcs);
+ PtcJumpTable.WriteDynamicTable(jumpTable);
+ }
+
+ private static InfoEntry ReadInfo(BinaryReader infosReader)
+ {
+ InfoEntry infoEntry = new InfoEntry();
+
+ infoEntry.Address = infosReader.ReadInt64();
+ infoEntry.HighCq = infosReader.ReadBoolean();
+ infoEntry.CodeLen = infosReader.ReadInt32();
+ infoEntry.RelocEntriesCount = infosReader.ReadInt32();
+
+ return infoEntry;
+ }
+
+ private static byte[] ReadCode(BinaryReader codesReader, int codeLen)
+ {
+ byte[] codeBuf = new byte[codeLen];
+
+ codesReader.Read(codeBuf, 0, codeLen);
+
+ return codeBuf;
+ }
+
+ private static RelocEntry[] GetRelocEntries(BinaryReader relocsReader, int relocEntriesCount)
+ {
+ RelocEntry[] relocEntries = new RelocEntry[relocEntriesCount];
+
+ for (int i = 0; i < relocEntriesCount; i++)
+ {
+ int position = relocsReader.ReadInt32();
+ int index = relocsReader.ReadInt32();
+
+ relocEntries[i] = new RelocEntry(position, index);
+ }
+
+ return relocEntries;
+ }
+
+ private static void PatchCode(Span<byte> code, RelocEntry[] relocEntries, IntPtr pageTablePointer, JumpTable jumpTable)
+ {
+ foreach (RelocEntry relocEntry in relocEntries)
+ {
+ ulong imm;
+
+ if (relocEntry.Index == PageTablePointerIndex)
+ {
+ imm = (ulong)pageTablePointer.ToInt64();
+ }
+ else if (relocEntry.Index == JumpPointerIndex)
+ {
+ imm = (ulong)jumpTable.JumpPointer.ToInt64();
+ }
+ else if (relocEntry.Index == DynamicPointerIndex)
+ {
+ imm = (ulong)jumpTable.DynamicPointer.ToInt64();
+ }
+ else if (Delegates.TryGetDelegateFuncPtrByIndex(relocEntry.Index, out IntPtr funcPtr))
+ {
+ imm = (ulong)funcPtr.ToInt64();
+ }
+ else
+ {
+ throw new Exception($"Unexpected reloc entry {relocEntry}.");
+ }
+
+ BinaryPrimitives.WriteUInt64LittleEndian(code.Slice(relocEntry.Position, 8), imm);
+ }
+ }
+
+ private static UnwindInfo ReadUnwindInfo(BinaryReader unwindInfosReader)
+ {
+ int pushEntriesLength = unwindInfosReader.ReadInt32();
+
+ UnwindPushEntry[] pushEntries = new UnwindPushEntry[pushEntriesLength];
+
+ for (int i = 0; i < pushEntriesLength; i++)
+ {
+ int pseudoOp = unwindInfosReader.ReadInt32();
+ int prologOffset = unwindInfosReader.ReadInt32();
+ int regIndex = unwindInfosReader.ReadInt32();
+ int stackOffsetOrAllocSize = unwindInfosReader.ReadInt32();
+
+ pushEntries[i] = new UnwindPushEntry((UnwindPseudoOp)pseudoOp, prologOffset, regIndex, stackOffsetOrAllocSize);
+ }
+
+ int prologueSize = unwindInfosReader.ReadInt32();
+
+ return new UnwindInfo(pushEntries, prologueSize);
+ }
+
+ private static TranslatedFunction FastTranslate(byte[] code, UnwindInfo unwindInfo, bool highCq)
+ {
+ CompiledFunction cFunc = new CompiledFunction(code, unwindInfo);
+
+ IntPtr codePtr = JitCache.Map(cFunc);
+
+ GuestFunction gFunc = Marshal.GetDelegateForFunctionPointer<GuestFunction>(codePtr);
+
+ TranslatedFunction tFunc = new TranslatedFunction(gFunc, highCq);
+
+ return tFunc;
+ }
+
+ internal static void MakeAndSaveTranslations(ConcurrentDictionary<ulong, TranslatedFunction> funcs, IMemoryManager memory, JumpTable jumpTable)
+ {
+ if (PtcProfiler.ProfiledFuncs.Count == 0)
+ {
+ return;
+ }
+
+ _translateCount = 0;
+ _rejitCount = 0;
+
+ ThreadPool.QueueUserWorkItem(TranslationLogger, (funcs.Count, PtcProfiler.ProfiledFuncs.Count));
+
+ int maxDegreeOfParallelism = (Environment.ProcessorCount * 3) / 4;
+
+ Parallel.ForEach(PtcProfiler.ProfiledFuncs, new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }, (item, state) =>
+ {
+ ulong address = item.Key;
+
+ Debug.Assert(PtcProfiler.IsAddressInStaticCodeRange(address));
+
+ if (!funcs.ContainsKey(address))
+ {
+ TranslatedFunction func = Translator.Translate(memory, jumpTable, address, item.Value.mode, item.Value.highCq);
+
+ funcs.TryAdd(address, func);
+
+ if (func.HighCq)
+ {
+ jumpTable.RegisterFunction(address, func);
+ }
+
+ Interlocked.Increment(ref _translateCount);
+ }
+ else if (item.Value.highCq && !funcs[address].HighCq)
+ {
+ TranslatedFunction func = Translator.Translate(memory, jumpTable, address, item.Value.mode, highCq: true);
+
+ funcs[address] = func;
+
+ jumpTable.RegisterFunction(address, func);
+
+ Interlocked.Increment(ref _rejitCount);
+ }
+
+ if (State != PtcState.Enabled)
+ {
+ state.Stop();
+ }
+ });
+
+ _loggerEvent.Set();
+
+ if (_translateCount != 0 || _rejitCount != 0)
+ {
+ PtcJumpTable.Initialize(jumpTable);
+
+ PtcJumpTable.ReadJumpTable(jumpTable);
+ PtcJumpTable.ReadDynamicTable(jumpTable);
+
+ ThreadPool.QueueUserWorkItem(PreSave);
+ }
+ }
+
+ private static void TranslationLogger(object state)
+ {
+ const int refreshRate = 1; // Seconds.
+
+ (int funcsCount, int ProfiledFuncsCount) = ((int, int))state;
+
+ do
+ {
+ Logger.PrintInfo(LogClass.Ptc, $"{funcsCount + _translateCount} of {ProfiledFuncsCount} functions to translate - {_rejitCount} functions rejited");
+ }
+ while (!_loggerEvent.WaitOne(refreshRate * 1000));
+
+ Logger.PrintInfo(LogClass.Ptc, $"{funcsCount + _translateCount} of {ProfiledFuncsCount} functions to translate - {_rejitCount} functions rejited");
+ }
+
+ internal static void WriteInfoCodeReloc(long address, bool highCq, PtcInfo ptcInfo)
+ {
+ lock (_lock)
+ {
+ // WriteInfo.
+ _infosWriter.Write((long)address); // InfoEntry.Address
+ _infosWriter.Write((bool)highCq); // InfoEntry.HighCq
+ _infosWriter.Write((int)ptcInfo.CodeStream.Length); // InfoEntry.CodeLen
+ _infosWriter.Write((int)ptcInfo.RelocEntriesCount); // InfoEntry.RelocEntriesCount
+
+ // WriteCode.
+ ptcInfo.CodeStream.WriteTo(_codesStream);
+
+ // WriteReloc.
+ ptcInfo.RelocStream.WriteTo(_relocsStream);
+
+ // WriteUnwindInfo.
+ ptcInfo.UnwindInfoStream.WriteTo(_unwindInfosStream);
+ }
+ }
+
+ private static ulong GetFeatureInfo()
+ {
+ ulong featureInfo = 0ul;
+
+ featureInfo |= (Sse3.IsSupported ? 1ul : 0ul) << 0;
+ featureInfo |= (Pclmulqdq.IsSupported ? 1ul : 0ul) << 1;
+ featureInfo |= (Ssse3.IsSupported ? 1ul : 0ul) << 9;
+ featureInfo |= (Fma.IsSupported ? 1ul : 0ul) << 12;
+ featureInfo |= (Sse41.IsSupported ? 1ul : 0ul) << 19;
+ featureInfo |= (Sse42.IsSupported ? 1ul : 0ul) << 20;
+ featureInfo |= (Popcnt.IsSupported ? 1ul : 0ul) << 23;
+ featureInfo |= (Aes.IsSupported ? 1ul : 0ul) << 25;
+ featureInfo |= (Avx.IsSupported ? 1ul : 0ul) << 28;
+ featureInfo |= (Sse.IsSupported ? 1ul : 0ul) << 57;
+ featureInfo |= (Sse2.IsSupported ? 1ul : 0ul) << 58;
+
+ return featureInfo;
+ }
+
+ private struct Header
+ {
+ public string Magic;
+
+ public int CacheFileVersion;
+ public ulong FeatureInfo;
+
+ public int InfosLen;
+ public int CodesLen;
+ public int RelocsLen;
+ public int UnwindInfosLen;
+ }
+
+ private struct InfoEntry
+ {
+ public const int Stride = 17; // Bytes.
+
+ public long Address;
+ public bool HighCq;
+ public int CodeLen;
+ public int RelocEntriesCount;
+ }
+
+ private static void Enable()
+ {
+ State = PtcState.Enabled;
+ }
+
+ public static void Continue()
+ {
+ if (State == PtcState.Enabled)
+ {
+ State = PtcState.Continuing;
+ }
+ }
+
+ public static void Close()
+ {
+ if (State == PtcState.Enabled ||
+ State == PtcState.Continuing)
+ {
+ State = PtcState.Closing;
+ }
+ }
+
+ internal static void Disable()
+ {
+ State = PtcState.Disabled;
+ }
+
+ private static void Wait()
+ {
+ _waitEvent.WaitOne();
+ }
+
+ public static void Dispose()
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+
+ AppDomain.CurrentDomain.UnhandledException -= CurrentDomain_UnhandledException;
+ AppDomain.CurrentDomain.ProcessExit -= CurrentDomain_ProcessExit;
+
+ Wait();
+ _waitEvent.Dispose();
+
+ _infosWriter.Dispose();
+
+ _infosStream.Dispose();
+ _codesStream.Dispose();
+ _relocsStream.Dispose();
+ _unwindInfosStream.Dispose();
+ }
+ }
+
+ private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
+ {
+ Close();
+ PtcProfiler.Stop();
+
+ if (e.IsTerminating)
+ {
+ Dispose();
+ PtcProfiler.Dispose();
+ }
+ }
+
+ private static void CurrentDomain_ProcessExit(object sender, EventArgs e)
+ {
+ Dispose();
+ PtcProfiler.Dispose();
+ }
+ }
+} \ No newline at end of file
diff --git a/ARMeilleure/Translation/PTC/PtcInfo.cs b/ARMeilleure/Translation/PTC/PtcInfo.cs
new file mode 100644
index 00000000..f03eb6ba
--- /dev/null
+++ b/ARMeilleure/Translation/PTC/PtcInfo.cs
@@ -0,0 +1,68 @@
+using ARMeilleure.CodeGen.Unwinding;
+using System;
+using System.IO;
+
+namespace ARMeilleure.Translation.PTC
+{
+ sealed class PtcInfo : IDisposable
+ {
+ private readonly BinaryWriter _relocWriter;
+ private readonly BinaryWriter _unwindInfoWriter;
+
+ public MemoryStream CodeStream { get; }
+ public MemoryStream RelocStream { get; }
+ public MemoryStream UnwindInfoStream { get; }
+
+ public int RelocEntriesCount { get; private set; }
+
+ public PtcInfo()
+ {
+ CodeStream = new MemoryStream();
+ RelocStream = new MemoryStream();
+ UnwindInfoStream = new MemoryStream();
+
+ _relocWriter = new BinaryWriter(RelocStream, EncodingCache.UTF8NoBOM, true);
+ _unwindInfoWriter = new BinaryWriter(UnwindInfoStream, EncodingCache.UTF8NoBOM, true);
+
+ RelocEntriesCount = 0;
+ }
+
+ public void WriteCode(MemoryStream codeStream)
+ {
+ codeStream.WriteTo(CodeStream);
+ }
+
+ public void WriteRelocEntry(RelocEntry relocEntry)
+ {
+ _relocWriter.Write((int)relocEntry.Position);
+ _relocWriter.Write((int)relocEntry.Index);
+
+ RelocEntriesCount++;
+ }
+
+ public void WriteUnwindInfo(UnwindInfo unwindInfo)
+ {
+ _unwindInfoWriter.Write((int)unwindInfo.PushEntries.Length);
+
+ foreach (UnwindPushEntry unwindPushEntry in unwindInfo.PushEntries)
+ {
+ _unwindInfoWriter.Write((int)unwindPushEntry.PseudoOp);
+ _unwindInfoWriter.Write((int)unwindPushEntry.PrologOffset);
+ _unwindInfoWriter.Write((int)unwindPushEntry.RegIndex);
+ _unwindInfoWriter.Write((int)unwindPushEntry.StackOffsetOrAllocSize);
+ }
+
+ _unwindInfoWriter.Write((int)unwindInfo.PrologSize);
+ }
+
+ public void Dispose()
+ {
+ _relocWriter.Dispose();
+ _unwindInfoWriter.Dispose();
+
+ CodeStream.Dispose();
+ RelocStream.Dispose();
+ UnwindInfoStream.Dispose();
+ }
+ }
+}
diff --git a/ARMeilleure/Translation/PTC/PtcJumpTable.cs b/ARMeilleure/Translation/PTC/PtcJumpTable.cs
new file mode 100644
index 00000000..0a3ae240
--- /dev/null
+++ b/ARMeilleure/Translation/PTC/PtcJumpTable.cs
@@ -0,0 +1,222 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace ARMeilleure.Translation.PTC
+{
+ [Serializable]
+ class PtcJumpTable
+ {
+ private readonly List<KeyValuePair<long, DirectHostAddress>> _jumpTable;
+ private readonly List<KeyValuePair<long, IndirectHostAddress>> _dynamicTable;
+
+ private readonly List<ulong> _targets;
+ private readonly Dictionary<ulong, LinkedList<int>> _dependants;
+
+ public int TableEnd => _jumpTable.Count;
+ public int DynTableEnd => _dynamicTable.Count;
+
+ public List<ulong> Targets => _targets;
+ public Dictionary<ulong, LinkedList<int>> Dependants => _dependants;
+
+ public PtcJumpTable()
+ {
+ _jumpTable = new List<KeyValuePair<long, DirectHostAddress>>();
+ _dynamicTable = new List<KeyValuePair<long, IndirectHostAddress>>();
+
+ _targets = new List<ulong>();
+ _dependants = new Dictionary<ulong, LinkedList<int>>();
+ }
+
+ public void Initialize(JumpTable jumpTable)
+ {
+ _targets.Clear();
+
+ foreach (ulong guestAddress in jumpTable.Targets.Keys)
+ {
+ _targets.Add(guestAddress);
+ }
+
+ _dependants.Clear();
+
+ foreach (var item in jumpTable.Dependants)
+ {
+ _dependants.Add(item.Key, new LinkedList<int>(item.Value));
+ }
+ }
+
+ public void Clear()
+ {
+ _jumpTable.Clear();
+ _dynamicTable.Clear();
+
+ _targets.Clear();
+ _dependants.Clear();
+ }
+
+ public void WriteJumpTable(JumpTable jumpTable, ConcurrentDictionary<ulong, TranslatedFunction> funcs)
+ {
+ jumpTable.ExpandIfNeededJumpTable(TableEnd);
+
+ int entry = 0;
+
+ foreach (var item in _jumpTable)
+ {
+ entry += 1;
+
+ long guestAddress = item.Key;
+ DirectHostAddress directHostAddress = item.Value;
+
+ long hostAddress;
+
+ if (directHostAddress == DirectHostAddress.CallStub)
+ {
+ hostAddress = DirectCallStubs.DirectCallStub(false).ToInt64();
+ }
+ else if (directHostAddress == DirectHostAddress.TailCallStub)
+ {
+ hostAddress = DirectCallStubs.DirectCallStub(true).ToInt64();
+ }
+ else if (directHostAddress == DirectHostAddress.Host)
+ {
+ if (funcs.TryGetValue((ulong)guestAddress, out TranslatedFunction func))
+ {
+ hostAddress = func.FuncPtr.ToInt64();
+ }
+ else
+ {
+ throw new KeyNotFoundException($"({nameof(guestAddress)} = 0x{(ulong)guestAddress:X16})");
+ }
+ }
+ else
+ {
+ throw new InvalidOperationException(nameof(directHostAddress));
+ }
+
+ IntPtr addr = jumpTable.GetEntryAddressJumpTable(entry);
+
+ Marshal.WriteInt64(addr, 0, guestAddress);
+ Marshal.WriteInt64(addr, 8, hostAddress);
+ }
+ }
+
+ public void WriteDynamicTable(JumpTable jumpTable)
+ {
+ if (JumpTable.DynamicTableElems > 1)
+ {
+ throw new NotSupportedException();
+ }
+
+ jumpTable.ExpandIfNeededDynamicTable(DynTableEnd);
+
+ int entry = 0;
+
+ foreach (var item in _dynamicTable)
+ {
+ entry += 1;
+
+ long guestAddress = item.Key;
+ IndirectHostAddress indirectHostAddress = item.Value;
+
+ long hostAddress;
+
+ if (indirectHostAddress == IndirectHostAddress.CallStub)
+ {
+ hostAddress = DirectCallStubs.IndirectCallStub(false).ToInt64();
+ }
+ else if (indirectHostAddress == IndirectHostAddress.TailCallStub)
+ {
+ hostAddress = DirectCallStubs.IndirectCallStub(true).ToInt64();
+ }
+ else
+ {
+ throw new InvalidOperationException(nameof(indirectHostAddress));
+ }
+
+ IntPtr addr = jumpTable.GetEntryAddressDynamicTable(entry);
+
+ Marshal.WriteInt64(addr, 0, guestAddress);
+ Marshal.WriteInt64(addr, 8, hostAddress);
+ }
+ }
+
+ public void ReadJumpTable(JumpTable jumpTable)
+ {
+ _jumpTable.Clear();
+
+ for (int entry = 1; entry <= jumpTable.TableEnd; entry++)
+ {
+ IntPtr addr = jumpTable.GetEntryAddressJumpTable(entry);
+
+ long guestAddress = Marshal.ReadInt64(addr, 0);
+ long hostAddress = Marshal.ReadInt64(addr, 8);
+
+ DirectHostAddress directHostAddress;
+
+ if (hostAddress == DirectCallStubs.DirectCallStub(false).ToInt64())
+ {
+ directHostAddress = DirectHostAddress.CallStub;
+ }
+ else if (hostAddress == DirectCallStubs.DirectCallStub(true).ToInt64())
+ {
+ directHostAddress = DirectHostAddress.TailCallStub;
+ }
+ else
+ {
+ directHostAddress = DirectHostAddress.Host;
+ }
+
+ _jumpTable.Add(new KeyValuePair<long, DirectHostAddress>(guestAddress, directHostAddress));
+ }
+ }
+
+ public void ReadDynamicTable(JumpTable jumpTable)
+ {
+ if (JumpTable.DynamicTableElems > 1)
+ {
+ throw new NotSupportedException();
+ }
+
+ _dynamicTable.Clear();
+
+ for (int entry = 1; entry <= jumpTable.DynTableEnd; entry++)
+ {
+ IntPtr addr = jumpTable.GetEntryAddressDynamicTable(entry);
+
+ long guestAddress = Marshal.ReadInt64(addr, 0);
+ long hostAddress = Marshal.ReadInt64(addr, 8);
+
+ IndirectHostAddress indirectHostAddress;
+
+ if (hostAddress == DirectCallStubs.IndirectCallStub(false).ToInt64())
+ {
+ indirectHostAddress = IndirectHostAddress.CallStub;
+ }
+ else if (hostAddress == DirectCallStubs.IndirectCallStub(true).ToInt64())
+ {
+ indirectHostAddress = IndirectHostAddress.TailCallStub;
+ }
+ else
+ {
+ throw new InvalidOperationException($"({nameof(hostAddress)} = 0x{hostAddress:X16})");
+ }
+
+ _dynamicTable.Add(new KeyValuePair<long, IndirectHostAddress>(guestAddress, indirectHostAddress));
+ }
+ }
+
+ private enum DirectHostAddress
+ {
+ CallStub,
+ TailCallStub,
+ Host
+ }
+
+ private enum IndirectHostAddress
+ {
+ CallStub,
+ TailCallStub
+ }
+ }
+} \ No newline at end of file
diff --git a/ARMeilleure/Translation/PTC/PtcProfiler.cs b/ARMeilleure/Translation/PTC/PtcProfiler.cs
new file mode 100644
index 00000000..dcc31275
--- /dev/null
+++ b/ARMeilleure/Translation/PTC/PtcProfiler.cs
@@ -0,0 +1,267 @@
+using ARMeilleure.State;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.IO.Compression;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Security.Cryptography;
+using System.Threading;
+
+namespace ARMeilleure.Translation.PTC
+{
+ public static class PtcProfiler
+ {
+ private const int SaveInterval = 30; // Seconds.
+
+ private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
+
+ private static readonly BinaryFormatter _binaryFormatter;
+
+ private static readonly System.Timers.Timer _timer;
+
+ private static readonly ManualResetEvent _waitEvent;
+
+ private static readonly object _lock;
+
+ private static bool _disposed;
+
+ internal static Dictionary<ulong, (ExecutionMode mode, bool highCq)> ProfiledFuncs { get; private set; } //! Not to be modified.
+
+ internal static bool Enabled { get; private set; }
+
+ public static ulong StaticCodeStart { internal get; set; }
+ public static int StaticCodeSize { internal get; set; }
+
+ static PtcProfiler()
+ {
+ _binaryFormatter = new BinaryFormatter();
+
+ _timer = new System.Timers.Timer((double)SaveInterval * 1000d);
+ _timer.Elapsed += PreSave;
+
+ _waitEvent = new ManualResetEvent(true);
+
+ _lock = new object();
+
+ _disposed = false;
+
+ ProfiledFuncs = new Dictionary<ulong, (ExecutionMode, bool)>();
+
+ Enabled = false;
+ }
+
+ internal static void AddEntry(ulong address, ExecutionMode mode, bool highCq)
+ {
+ if (IsAddressInStaticCodeRange(address))
+ {
+ lock (_lock)
+ {
+ Debug.Assert(!highCq && !ProfiledFuncs.ContainsKey(address));
+
+ ProfiledFuncs.TryAdd(address, (mode, highCq));
+ }
+ }
+ }
+
+ internal static void UpdateEntry(ulong address, ExecutionMode mode, bool highCq)
+ {
+ if (IsAddressInStaticCodeRange(address))
+ {
+ lock (_lock)
+ {
+ Debug.Assert(highCq && ProfiledFuncs.ContainsKey(address));
+
+ ProfiledFuncs[address] = (mode, highCq);
+ }
+ }
+ }
+
+ internal static bool IsAddressInStaticCodeRange(ulong address)
+ {
+ return address >= StaticCodeStart && address < StaticCodeStart + (ulong)StaticCodeSize;
+ }
+
+ internal static void ClearEntries()
+ {
+ ProfiledFuncs.Clear();
+ }
+
+ internal static void PreLoad()
+ {
+ string fileNameActual = String.Concat(Ptc.CachePathActual, ".info");
+ string fileNameBackup = String.Concat(Ptc.CachePathBackup, ".info");
+
+ FileInfo fileInfoActual = new FileInfo(fileNameActual);
+ FileInfo fileInfoBackup = new FileInfo(fileNameBackup);
+
+ if (fileInfoActual.Exists && fileInfoActual.Length != 0L)
+ {
+ if (!Load(fileNameActual))
+ {
+ if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
+ {
+ Load(fileNameBackup);
+ }
+ }
+ }
+ else if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
+ {
+ Load(fileNameBackup);
+ }
+ }
+
+ private static bool Load(string fileName)
+ {
+ using (FileStream compressedStream = new FileStream(fileName, FileMode.Open))
+ using (DeflateStream deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress, true))
+ using (MemoryStream stream = new MemoryStream())
+ using (MD5 md5 = MD5.Create())
+ {
+ int hashSize = md5.HashSize / 8;
+
+ deflateStream.CopyTo(stream);
+
+ stream.Seek(0L, SeekOrigin.Begin);
+
+ byte[] currentHash = new byte[hashSize];
+ stream.Read(currentHash, 0, hashSize);
+
+ byte[] expectedHash = md5.ComputeHash(stream);
+
+ if (!CompareHash(currentHash, expectedHash))
+ {
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ stream.Seek((long)hashSize, SeekOrigin.Begin);
+
+ try
+ {
+ ProfiledFuncs = (Dictionary<ulong, (ExecutionMode, bool)>)_binaryFormatter.Deserialize(stream);
+ }
+ catch
+ {
+ ProfiledFuncs = new Dictionary<ulong, (ExecutionMode, bool)>();
+
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ private static bool CompareHash(ReadOnlySpan<byte> currentHash, ReadOnlySpan<byte> expectedHash)
+ {
+ return currentHash.SequenceEqual(expectedHash);
+ }
+
+ private static void InvalidateCompressedStream(FileStream compressedStream)
+ {
+ compressedStream.SetLength(0L);
+ }
+
+ private static void PreSave(object source, System.Timers.ElapsedEventArgs e)
+ {
+ _waitEvent.Reset();
+
+ string fileNameActual = String.Concat(Ptc.CachePathActual, ".info");
+ string fileNameBackup = String.Concat(Ptc.CachePathBackup, ".info");
+
+ FileInfo fileInfoActual = new FileInfo(fileNameActual);
+
+ if (fileInfoActual.Exists && fileInfoActual.Length != 0L)
+ {
+ File.Copy(fileNameActual, fileNameBackup, true);
+ }
+
+ Save(fileNameActual);
+
+ _waitEvent.Set();
+ }
+
+ private static void Save(string fileName)
+ {
+ using (MemoryStream stream = new MemoryStream())
+ using (MD5 md5 = MD5.Create())
+ {
+ int hashSize = md5.HashSize / 8;
+
+ stream.Seek((long)hashSize, SeekOrigin.Begin);
+
+ lock (_lock)
+ {
+ _binaryFormatter.Serialize(stream, ProfiledFuncs);
+ }
+
+ stream.Seek((long)hashSize, SeekOrigin.Begin);
+ byte[] hash = md5.ComputeHash(stream);
+
+ stream.Seek(0L, SeekOrigin.Begin);
+ stream.Write(hash, 0, hashSize);
+
+ using (FileStream compressedStream = new FileStream(fileName, FileMode.OpenOrCreate))
+ using (DeflateStream deflateStream = new DeflateStream(compressedStream, SaveCompressionLevel, true))
+ {
+ try
+ {
+ stream.WriteTo(deflateStream);
+ }
+ catch
+ {
+ compressedStream.Position = 0L;
+ }
+
+ if (compressedStream.Position < compressedStream.Length)
+ {
+ compressedStream.SetLength(compressedStream.Position);
+ }
+ }
+ }
+ }
+
+ internal static void Start()
+ {
+ if (Ptc.State == PtcState.Enabled ||
+ Ptc.State == PtcState.Continuing)
+ {
+ Enabled = true;
+
+ _timer.Enabled = true;
+ }
+ }
+
+ public static void Stop()
+ {
+ Enabled = false;
+
+ if (!_disposed)
+ {
+ _timer.Enabled = false;
+ }
+ }
+
+ internal static void Wait()
+ {
+ _waitEvent.WaitOne();
+ }
+
+ public static void Dispose()
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+
+ _timer.Elapsed -= PreSave;
+ _timer.Dispose();
+
+ Wait();
+ _waitEvent.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/ARMeilleure/Translation/PTC/PtcState.cs b/ARMeilleure/Translation/PTC/PtcState.cs
new file mode 100644
index 00000000..ca4f4108
--- /dev/null
+++ b/ARMeilleure/Translation/PTC/PtcState.cs
@@ -0,0 +1,10 @@
+namespace ARMeilleure.Translation.PTC
+{
+ enum PtcState
+ {
+ Enabled,
+ Continuing,
+ Closing,
+ Disabled
+ }
+} \ No newline at end of file
diff --git a/ARMeilleure/Translation/PTC/RelocEntry.cs b/ARMeilleure/Translation/PTC/RelocEntry.cs
new file mode 100644
index 00000000..3d729fbb
--- /dev/null
+++ b/ARMeilleure/Translation/PTC/RelocEntry.cs
@@ -0,0 +1,19 @@
+namespace ARMeilleure.Translation.PTC
+{
+ struct RelocEntry
+ {
+ public int Position;
+ public int Index;
+
+ public RelocEntry(int position, int index)
+ {
+ Position = position;
+ Index = index;
+ }
+
+ public override string ToString()
+ {
+ return $"({nameof(Position)} = {Position}, {nameof(Index)} = {Index})";
+ }
+ }
+}
diff --git a/ARMeilleure/Translation/PriorityQueue.cs b/ARMeilleure/Translation/PriorityQueue.cs
deleted file mode 100644
index 000a5009..00000000
--- a/ARMeilleure/Translation/PriorityQueue.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System.Collections.Concurrent;
-
-namespace ARMeilleure.Translation
-{
- class PriorityQueue<T>
- {
- private ConcurrentStack<T>[] _queues;
-
- public PriorityQueue(int priorities)
- {
- _queues = new ConcurrentStack<T>[priorities];
-
- for (int index = 0; index < priorities; index++)
- {
- _queues[index] = new ConcurrentStack<T>();
- }
- }
-
- public void Enqueue(int priority, T value)
- {
- _queues[priority].Push(value);
- }
-
- public bool TryDequeue(out T value)
- {
- for (int index = 0; index < _queues.Length; index++)
- {
- if (_queues[index].TryPop(out value))
- {
- return true;
- }
- }
-
- value = default(T);
-
- return false;
- }
- }
-} \ No newline at end of file
diff --git a/ARMeilleure/Translation/RejitRequest.cs b/ARMeilleure/Translation/RejitRequest.cs
index e0b0e0b9..1bed5c0a 100644
--- a/ARMeilleure/Translation/RejitRequest.cs
+++ b/ARMeilleure/Translation/RejitRequest.cs
@@ -1,4 +1,4 @@
-using ARMeilleure.State;
+using ARMeilleure.State;
namespace ARMeilleure.Translation
{
diff --git a/ARMeilleure/Translation/TranslatedFunction.cs b/ARMeilleure/Translation/TranslatedFunction.cs
index f1dc6dee..36fae50a 100644
--- a/ARMeilleure/Translation/TranslatedFunction.cs
+++ b/ARMeilleure/Translation/TranslatedFunction.cs
@@ -4,22 +4,23 @@ using System.Threading;
namespace ARMeilleure.Translation
{
- class TranslatedFunction
+ sealed class TranslatedFunction
{
private const int MinCallsForRejit = 100;
- private GuestFunction _func;
- private IntPtr _funcPtr;
+ private readonly GuestFunction _func; // Ensure that this delegate will not be garbage collected.
- private bool _rejit;
- private int _callCount;
+ private int _callCount = 0;
- public bool HighCq => !_rejit;
+ public bool HighCq { get; }
+ public IntPtr FuncPtr { get; }
- public TranslatedFunction(GuestFunction func, bool rejit)
+ public TranslatedFunction(GuestFunction func, bool highCq)
{
- _func = func;
- _rejit = rejit;
+ _func = func;
+
+ HighCq = highCq;
+ FuncPtr = Marshal.GetFunctionPointerForDelegate<GuestFunction>(func);
}
public ulong Execute(State.ExecutionContext context)
@@ -29,17 +30,7 @@ namespace ARMeilleure.Translation
public bool ShouldRejit()
{
- return _rejit && Interlocked.Increment(ref _callCount) == MinCallsForRejit;
- }
-
- public IntPtr GetPointer()
- {
- if (_funcPtr == IntPtr.Zero)
- {
- _funcPtr = Marshal.GetFunctionPointerForDelegate(_func);
- }
-
- return _funcPtr;
+ return !HighCq && Interlocked.Increment(ref _callCount) == MinCallsForRejit;
}
}
} \ No newline at end of file
diff --git a/ARMeilleure/Translation/Translator.cs b/ARMeilleure/Translation/Translator.cs
index 700b54c2..1c2ead4f 100644
--- a/ARMeilleure/Translation/Translator.cs
+++ b/ARMeilleure/Translation/Translator.cs
@@ -13,22 +13,22 @@ using static ARMeilleure.IntermediateRepresentation.OperationHelper;
namespace ARMeilleure.Translation
{
+ using PTC;
+
public class Translator
{
private const ulong CallFlag = InstEmitFlowHelper.CallFlag;
- private const bool AlwaysTranslateFunctions = true; // If false, only translates a single block for lowCq.
-
private readonly IMemoryManager _memory;
private readonly ConcurrentDictionary<ulong, TranslatedFunction> _funcs;
- private readonly JumpTable _jumpTable;
-
- private readonly PriorityQueue<RejitRequest> _backgroundQueue;
+ private readonly ConcurrentStack<RejitRequest> _backgroundStack;
private readonly AutoResetEvent _backgroundTranslatorEvent;
+ private readonly JumpTable _jumpTable;
+
private volatile int _threadCount;
public Translator(IJitMemoryAllocator allocator, IMemoryManager memory)
@@ -37,32 +37,45 @@ namespace ARMeilleure.Translation
_funcs = new ConcurrentDictionary<ulong, TranslatedFunction>();
- _jumpTable = new JumpTable(allocator);
-
- _backgroundQueue = new PriorityQueue<RejitRequest>(2);
+ _backgroundStack = new ConcurrentStack<RejitRequest>();
_backgroundTranslatorEvent = new AutoResetEvent(false);
+ _jumpTable = new JumpTable(allocator);
+
JitCache.Initialize(allocator);
+
DirectCallStubs.InitializeStubs();
+
+ if (Ptc.State == PtcState.Enabled)
+ {
+ Ptc.LoadTranslations(_funcs, memory.PageTablePointer, _jumpTable);
+ }
}
- private void TranslateQueuedSubs()
+ private void TranslateStackedSubs()
{
while (_threadCount != 0)
{
- if (_backgroundQueue.TryDequeue(out RejitRequest request))
+ if (_backgroundStack.TryPop(out RejitRequest request))
{
- TranslatedFunction func = Translate(request.Address, request.Mode, highCq: true);
+ TranslatedFunction func = Translate(_memory, _jumpTable, request.Address, request.Mode, highCq: true);
_funcs.AddOrUpdate(request.Address, func, (key, oldFunc) => func);
+
_jumpTable.RegisterFunction(request.Address, func);
+
+ if (PtcProfiler.Enabled)
+ {
+ PtcProfiler.UpdateEntry(request.Address, request.Mode, highCq: true);
+ }
}
else
{
_backgroundTranslatorEvent.WaitOne();
}
}
+
_backgroundTranslatorEvent.Set(); // Wake up any other background translator threads, to encourage them to exit.
}
@@ -70,16 +83,27 @@ namespace ARMeilleure.Translation
{
if (Interlocked.Increment(ref _threadCount) == 1)
{
+ if (Ptc.State == PtcState.Enabled)
+ {
+ Ptc.MakeAndSaveTranslations(_funcs, _memory, _jumpTable);
+ }
+
+ PtcProfiler.Start();
+
+ Ptc.Disable();
+
// Simple heuristic, should be user configurable in future. (1 for 4 core/ht or less, 2 for 6 core+ht etc).
// All threads are normal priority except from the last, which just fills as much of the last core as the os lets it with a low priority.
// If we only have one rejit thread, it should be normal priority as highCq code is performance critical.
// TODO: Use physical cores rather than logical. This only really makes sense for processors with hyperthreading. Requires OS specific code.
int unboundedThreadCount = Math.Max(1, (Environment.ProcessorCount - 6) / 3);
- int threadCount = Math.Min(4, unboundedThreadCount);
+ int threadCount = Math.Min(4, unboundedThreadCount);
+
for (int i = 0; i < threadCount; i++)
{
bool last = i != 0 && i == unboundedThreadCount - 1;
- Thread backgroundTranslatorThread = new Thread(TranslateQueuedSubs)
+
+ Thread backgroundTranslatorThread = new Thread(TranslateStackedSubs)
{
Name = "CPU.BackgroundTranslatorThread." + i,
Priority = last ? ThreadPriority.Lowest : ThreadPriority.Normal
@@ -130,13 +154,19 @@ namespace ARMeilleure.Translation
if (!_funcs.TryGetValue(address, out TranslatedFunction func))
{
- func = Translate(address, mode, highCq: false);
+ func = Translate(_memory, _jumpTable, address, mode, highCq: false);
_funcs.TryAdd(address, func);
+
+ if (PtcProfiler.Enabled)
+ {
+ PtcProfiler.AddEntry(address, mode, highCq: false);
+ }
}
- else if (isCallTarget && func.ShouldRejit())
+
+ if (isCallTarget && func.ShouldRejit())
{
- _backgroundQueue.Enqueue(0, new RejitRequest(address, mode));
+ _backgroundStack.Push(new RejitRequest(address, mode));
_backgroundTranslatorEvent.Set();
}
@@ -144,18 +174,16 @@ namespace ARMeilleure.Translation
return func;
}
- private TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq)
+ internal static TranslatedFunction Translate(IMemoryManager memory, JumpTable jumpTable, ulong address, ExecutionMode mode, bool highCq)
{
- ArmEmitterContext context = new ArmEmitterContext(_memory, _jumpTable, (long)address, highCq, Aarch32Mode.User);
+ ArmEmitterContext context = new ArmEmitterContext(memory, jumpTable, (long)address, highCq, Aarch32Mode.User);
PrepareOperandPool(highCq);
PrepareOperationPool(highCq);
Logger.StartPass(PassName.Decoding);
- Block[] blocks = AlwaysTranslateFunctions
- ? Decoder.DecodeFunction (_memory, address, mode, highCq)
- : Decoder.DecodeBasicBlock(_memory, address, mode);
+ Block[] blocks = Decoder.DecodeFunction(memory, address, mode, highCq);
Logger.EndPass(PassName.Decoding);
@@ -182,12 +210,26 @@ namespace ARMeilleure.Translation
CompilerOptions options = highCq ? CompilerOptions.HighCq : CompilerOptions.None;
- GuestFunction func = Compiler.Compile<GuestFunction>(cfg, argTypes, OperandType.I64, options);
+ GuestFunction func;
+
+ if (Ptc.State == PtcState.Disabled)
+ {
+ func = Compiler.Compile<GuestFunction>(cfg, argTypes, OperandType.I64, options);
+ }
+ else
+ {
+ using (PtcInfo ptcInfo = new PtcInfo())
+ {
+ func = Compiler.Compile<GuestFunction>(cfg, argTypes, OperandType.I64, options, ptcInfo);
+
+ Ptc.WriteInfoCodeReloc((long)address, highCq, ptcInfo);
+ }
+ }
ResetOperandPool(highCq);
ResetOperationPool(highCq);
- return new TranslatedFunction(func, rejit: !highCq);
+ return new TranslatedFunction(func, highCq);
}
private static ControlFlowGraph EmitAndGetCFG(ArmEmitterContext context, Block[] blocks)
@@ -264,7 +306,7 @@ namespace ARMeilleure.Translation
context.BranchIfTrue(lblNonZero, count);
- Operand running = context.Call(new _Bool(NativeInterface.CheckSynchronization));
+ Operand running = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.CheckSynchronization)));
context.BranchIfTrue(lblExit, running);
@@ -281,4 +323,4 @@ namespace ARMeilleure.Translation
context.MarkLabel(lblExit);
}
}
-} \ No newline at end of file
+}