AddressTable.cs 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. using ARMeilleure.Diagnostics.EventSources;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Runtime.InteropServices;
  5. namespace ARMeilleure.Common
  6. {
  7. /// <summary>
  8. /// Represents a table of guest address to a value.
  9. /// </summary>
  10. /// <typeparam name="TEntry">Type of the value</typeparam>
  11. unsafe class AddressTable<TEntry> : IDisposable where TEntry : unmanaged
  12. {
  13. /// <summary>
  14. /// Represents a level in an <see cref="AddressTable{TEntry}"/>.
  15. /// </summary>
  16. public readonly struct Level
  17. {
  18. /// <summary>
  19. /// Gets the index of the <see cref="Level"/> in the guest address.
  20. /// </summary>
  21. public int Index { get; }
  22. /// <summary>
  23. /// Gets the length of the <see cref="Level"/> in the guest address.
  24. /// </summary>
  25. public int Length { get; }
  26. /// <summary>
  27. /// Gets the mask which masks the bits used by the <see cref="Level"/>.
  28. /// </summary>
  29. public ulong Mask => ((1ul << Length) - 1) << Index;
  30. /// <summary>
  31. /// Initializes a new instance of the <see cref="Level"/> structure with the specified
  32. /// <paramref name="index"/> and <paramref name="length"/>.
  33. /// </summary>
  34. /// <param name="index">Index of the <see cref="Level"/></param>
  35. /// <param name="length">Length of the <see cref="Level"/></param>
  36. public Level(int index, int length)
  37. {
  38. (Index, Length) = (index, length);
  39. }
  40. /// <summary>
  41. /// Gets the value of the <see cref="Level"/> from the specified guest <paramref name="address"/>.
  42. /// </summary>
  43. /// <param name="address">Guest address</param>
  44. /// <returns>Value of the <see cref="Level"/> from the specified guest <paramref name="address"/></returns>
  45. public int GetValue(ulong address)
  46. {
  47. return (int)((address & Mask) >> Index);
  48. }
  49. }
  50. private bool _disposed;
  51. private TEntry** _table;
  52. private readonly List<IntPtr> _pages;
  53. /// <summary>
  54. /// Gets the bits used by the <see cref="Levels"/> of the <see cref="AddressTable{TEntry}"/> instance.
  55. /// </summary>
  56. public ulong Mask { get; }
  57. /// <summary>
  58. /// Gets the <see cref="Level"/>s used by the <see cref="AddressTable{TEntry}"/> instance.
  59. /// </summary>
  60. public Level[] Levels { get; }
  61. /// <summary>
  62. /// Gets or sets the default fill value of newly created leaf pages.
  63. /// </summary>
  64. public TEntry Fill { get; set; }
  65. /// <summary>
  66. /// Gets the base address of the <see cref="EntryTable{TEntry}"/>.
  67. /// </summary>
  68. /// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
  69. public IntPtr Base
  70. {
  71. get
  72. {
  73. if (_disposed)
  74. {
  75. throw new ObjectDisposedException(null);
  76. }
  77. lock (_pages)
  78. {
  79. return (IntPtr)GetRootPage();
  80. }
  81. }
  82. }
  83. /// <summary>
  84. /// Constructs a new instance of the <see cref="AddressTable{TEntry}"/> class with the specified list of
  85. /// <see cref="Level"/>.
  86. /// </summary>
  87. /// <exception cref="ArgumentNullException"><paramref name="levels"/> is null</exception>
  88. /// <exception cref="ArgumentException">Length of <paramref name="levels"/> is less than 2</exception>
  89. public AddressTable(Level[] levels)
  90. {
  91. if (levels == null)
  92. {
  93. throw new ArgumentNullException(nameof(levels));
  94. }
  95. if (levels.Length < 2)
  96. {
  97. throw new ArgumentException("Table must be at least 2 levels deep.", nameof(levels));
  98. }
  99. _pages = new List<IntPtr>(capacity: 16);
  100. Levels = levels;
  101. Mask = 0;
  102. foreach (var level in Levels)
  103. {
  104. Mask |= level.Mask;
  105. }
  106. }
  107. /// <summary>
  108. /// Determines if the specified <paramref name="address"/> is in the range of the
  109. /// <see cref="AddressTable{TEntry}"/>.
  110. /// </summary>
  111. /// <param name="address">Guest address</param>
  112. /// <returns><see langword="true"/> if is valid; otherwise <see langword="false"/></returns>
  113. public bool IsValid(ulong address)
  114. {
  115. return (address & ~Mask) == 0;
  116. }
  117. /// <summary>
  118. /// Gets a reference to the value at the specified guest <paramref name="address"/>.
  119. /// </summary>
  120. /// <param name="address">Guest address</param>
  121. /// <returns>Reference to the value at the specified guest <paramref name="address"/></returns>
  122. /// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
  123. /// <exception cref="ArgumentException"><paramref name="address"/> is not mapped</exception>
  124. public ref TEntry GetValue(ulong address)
  125. {
  126. if (_disposed)
  127. {
  128. throw new ObjectDisposedException(null);
  129. }
  130. if (!IsValid(address))
  131. {
  132. throw new ArgumentException($"Address 0x{address:X} is not mapped onto the table.", nameof(address));
  133. }
  134. lock (_pages)
  135. {
  136. return ref GetPage(address)[Levels[^1].GetValue(address)];
  137. }
  138. }
  139. /// <summary>
  140. /// Gets the leaf page for the specified guest <paramref name="address"/>.
  141. /// </summary>
  142. /// <param name="address">Guest address</param>
  143. /// <returns>Leaf page for the specified guest <paramref name="address"/></returns>
  144. private TEntry* GetPage(ulong address)
  145. {
  146. TEntry** page = GetRootPage();
  147. for (int i = 0; i < Levels.Length - 1; i++)
  148. {
  149. ref Level level = ref Levels[i];
  150. ref TEntry* nextPage = ref page[level.GetValue(address)];
  151. if (nextPage == null)
  152. {
  153. ref Level nextLevel = ref Levels[i + 1];
  154. nextPage = i == Levels.Length - 2 ?
  155. (TEntry*)Allocate(1 << nextLevel.Length, Fill, leaf: true) :
  156. (TEntry*)Allocate(1 << nextLevel.Length, IntPtr.Zero, leaf: false);
  157. }
  158. page = (TEntry**)nextPage;
  159. }
  160. return (TEntry*)page;
  161. }
  162. /// <summary>
  163. /// Lazily initialize and get the root page of the <see cref="AddressTable{TEntry}"/>.
  164. /// </summary>
  165. /// <returns>Root page of the <see cref="AddressTable{TEntry}"/></returns>
  166. private TEntry** GetRootPage()
  167. {
  168. if (_table == null)
  169. {
  170. _table = (TEntry**)Allocate(1 << Levels[0].Length, fill: IntPtr.Zero, leaf: false);
  171. }
  172. return _table;
  173. }
  174. /// <summary>
  175. /// Allocates a block of memory of the specified type and length.
  176. /// </summary>
  177. /// <typeparam name="T">Type of elements</typeparam>
  178. /// <param name="length">Number of elements</param>
  179. /// <param name="fill">Fill value</param>
  180. /// <param name="leaf"><see langword="true"/> if leaf; otherwise <see langword=""="false"/></param>
  181. /// <returns>Allocated block</returns>
  182. private IntPtr Allocate<T>(int length, T fill, bool leaf) where T : unmanaged
  183. {
  184. var size = sizeof(T) * length;
  185. var page = (IntPtr)NativeAllocator.Instance.Allocate((uint)size);
  186. var span = new Span<T>((void*)page, length);
  187. span.Fill(fill);
  188. _pages.Add(page);
  189. AddressTableEventSource.Log.Allocated(size, leaf);
  190. return page;
  191. }
  192. /// <summary>
  193. /// Releases all resources used by the <see cref="AddressTable{TEntry}"/> instance.
  194. /// </summary>
  195. public void Dispose()
  196. {
  197. Dispose(true);
  198. GC.SuppressFinalize(this);
  199. }
  200. /// <summary>
  201. /// Releases all unmanaged and optionally managed resources used by the <see cref="AddressTable{TEntry}"/>
  202. /// instance.
  203. /// </summary>
  204. /// <param name="disposing"><see langword="true"/> to dispose managed resources also; otherwise just unmanaged resouces</param>
  205. protected virtual void Dispose(bool disposing)
  206. {
  207. if (!_disposed)
  208. {
  209. foreach (var page in _pages)
  210. {
  211. Marshal.FreeHGlobal(page);
  212. }
  213. _disposed = true;
  214. }
  215. }
  216. /// <summary>
  217. /// Frees resources used by the <see cref="AddressTable{TEntry}"/> instance.
  218. /// </summary>
  219. ~AddressTable()
  220. {
  221. Dispose(false);
  222. }
  223. }
  224. }