CompareExchange128.cs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. using System;
  2. using System.Runtime.InteropServices;
  3. namespace ChocolArm64.Memory
  4. {
  5. static class CompareExchange128
  6. {
  7. private struct Int128
  8. {
  9. public ulong Low { get; }
  10. public ulong High { get; }
  11. public Int128(ulong low, ulong high)
  12. {
  13. Low = low;
  14. High = high;
  15. }
  16. }
  17. private delegate Int128 InterlockedCompareExchange(IntPtr address, Int128 expected, Int128 desired);
  18. private delegate int GetCpuId();
  19. private static InterlockedCompareExchange _interlockedCompareExchange;
  20. static CompareExchange128()
  21. {
  22. if (RuntimeInformation.OSArchitecture != Architecture.X64 || !IsCmpxchg16bSupported())
  23. {
  24. throw new PlatformNotSupportedException();
  25. }
  26. byte[] interlockedCompareExchange128Code;
  27. if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
  28. {
  29. interlockedCompareExchange128Code = new byte[]
  30. {
  31. 0x53, // push rbx
  32. 0x49, 0x8b, 0x00, // mov rax, [r8]
  33. 0x49, 0x8b, 0x19, // mov rbx, [r9]
  34. 0x49, 0x89, 0xca, // mov r10, rcx
  35. 0x49, 0x89, 0xd3, // mov r11, rdx
  36. 0x49, 0x8b, 0x49, 0x08, // mov rcx, [r9+8]
  37. 0x49, 0x8b, 0x50, 0x08, // mov rdx, [r8+8]
  38. 0xf0, 0x49, 0x0f, 0xc7, 0x0b, // lock cmpxchg16b [r11]
  39. 0x49, 0x89, 0x02, // mov [r10], rax
  40. 0x4c, 0x89, 0xd0, // mov rax, r10
  41. 0x49, 0x89, 0x52, 0x08, // mov [r10+8], rdx
  42. 0x5b, // pop rbx
  43. 0xc3 // ret
  44. };
  45. }
  46. else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
  47. RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
  48. {
  49. interlockedCompareExchange128Code = new byte[]
  50. {
  51. 0x53, // push rbx
  52. 0x49, 0x89, 0xd1, // mov r9, rdx
  53. 0x48, 0x89, 0xcb, // mov rbx, rcx
  54. 0x48, 0x89, 0xf0, // mov rax, rsi
  55. 0x4c, 0x89, 0xca, // mov rdx, r9
  56. 0x4c, 0x89, 0xc1, // mov rcx, r8
  57. 0xf0, 0x48, 0x0f, 0xc7, 0x0f, // lock cmpxchg16b [rdi]
  58. 0x5b, // pop rbx
  59. 0xc3 // ret
  60. };
  61. }
  62. else
  63. {
  64. throw new PlatformNotSupportedException();
  65. }
  66. IntPtr funcPtr = MapCodeAsExecutable(interlockedCompareExchange128Code);
  67. _interlockedCompareExchange = Marshal.GetDelegateForFunctionPointer<InterlockedCompareExchange>(funcPtr);
  68. }
  69. private static bool IsCmpxchg16bSupported()
  70. {
  71. byte[] getCpuIdCode = new byte[]
  72. {
  73. 0x53, // push rbx
  74. 0xb8, 0x01, 0x00, 0x00, 0x00, // mov eax, 0x1
  75. 0x0f, 0xa2, // cpuid
  76. 0x89, 0xc8, // mov eax, ecx
  77. 0x5b, // pop rbx
  78. 0xc3 // ret
  79. };
  80. IntPtr funcPtr = MapCodeAsExecutable(getCpuIdCode);
  81. GetCpuId getCpuId = Marshal.GetDelegateForFunctionPointer<GetCpuId>(funcPtr);
  82. int cpuId = getCpuId();
  83. MemoryManagement.Free(funcPtr);
  84. return (cpuId & (1 << 13)) != 0;
  85. }
  86. private static IntPtr MapCodeAsExecutable(byte[] code)
  87. {
  88. ulong codeLength = (ulong)code.Length;
  89. IntPtr funcPtr = MemoryManagement.Allocate(codeLength);
  90. unsafe
  91. {
  92. fixed (byte* codePtr = code)
  93. {
  94. byte* dest = (byte*)funcPtr;
  95. long size = (long)codeLength;
  96. Buffer.MemoryCopy(codePtr, dest, size, size);
  97. }
  98. }
  99. MemoryManagement.Reprotect(funcPtr, codeLength, MemoryProtection.Execute);
  100. return funcPtr;
  101. }
  102. public static bool InterlockedCompareExchange128(
  103. IntPtr address,
  104. ulong expectedLow,
  105. ulong expectedHigh,
  106. ulong desiredLow,
  107. ulong desiredHigh)
  108. {
  109. Int128 expected = new Int128(expectedLow, expectedHigh);
  110. Int128 desired = new Int128(desiredLow, desiredHigh);
  111. Int128 old = _interlockedCompareExchange(address, expected, desired);
  112. return old.Low == expected.Low && old.High == expected.High;
  113. }
  114. public static void InterlockedRead128(IntPtr address, out ulong low, out ulong high)
  115. {
  116. Int128 zero = new Int128(0, 0);
  117. Int128 old = _interlockedCompareExchange(address, zero, zero);
  118. low = old.Low;
  119. high = old.High;
  120. }
  121. }
  122. }