/ src / ARMeilleure / Translation / TranslatorStubs.cs
TranslatorStubs.cs
  1  using ARMeilleure.Common;
  2  using ARMeilleure.Instructions;
  3  using ARMeilleure.IntermediateRepresentation;
  4  using ARMeilleure.State;
  5  using ARMeilleure.Translation.Cache;
  6  using System;
  7  using System.Reflection;
  8  using System.Runtime.InteropServices;
  9  using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
 10  
 11  namespace ARMeilleure.Translation
 12  {
 13      /// <summary>
 14      /// Represents a stub manager.
 15      /// </summary>
 16      class TranslatorStubs : IDisposable
 17      {
 18          private readonly Lazy<IntPtr> _slowDispatchStub;
 19  
 20          private bool _disposed;
 21  
 22          private readonly AddressTable<ulong> _functionTable;
 23          private readonly Lazy<IntPtr> _dispatchStub;
 24          private readonly Lazy<DispatcherFunction> _dispatchLoop;
 25          private readonly Lazy<WrapperFunction> _contextWrapper;
 26  
 27          /// <summary>
 28          /// Gets the dispatch stub.
 29          /// </summary>
 30          /// <exception cref="ObjectDisposedException"><see cref="TranslatorStubs"/> instance was disposed</exception>
 31          public IntPtr DispatchStub
 32          {
 33              get
 34              {
 35                  ObjectDisposedException.ThrowIf(_disposed, this);
 36  
 37                  return _dispatchStub.Value;
 38              }
 39          }
 40  
 41          /// <summary>
 42          /// Gets the slow dispatch stub.
 43          /// </summary>
 44          /// <exception cref="ObjectDisposedException"><see cref="TranslatorStubs"/> instance was disposed</exception>
 45          public IntPtr SlowDispatchStub
 46          {
 47              get
 48              {
 49                  ObjectDisposedException.ThrowIf(_disposed, this);
 50  
 51                  return _slowDispatchStub.Value;
 52              }
 53          }
 54  
 55          /// <summary>
 56          /// Gets the dispatch loop function.
 57          /// </summary>
 58          /// <exception cref="ObjectDisposedException"><see cref="TranslatorStubs"/> instance was disposed</exception>
 59          public DispatcherFunction DispatchLoop
 60          {
 61              get
 62              {
 63                  ObjectDisposedException.ThrowIf(_disposed, this);
 64  
 65                  return _dispatchLoop.Value;
 66              }
 67          }
 68  
 69          /// <summary>
 70          /// Gets the context wrapper function.
 71          /// </summary>
 72          /// <exception cref="ObjectDisposedException"><see cref="TranslatorStubs"/> instance was disposed</exception>
 73          public WrapperFunction ContextWrapper
 74          {
 75              get
 76              {
 77                  ObjectDisposedException.ThrowIf(_disposed, this);
 78  
 79                  return _contextWrapper.Value;
 80              }
 81          }
 82  
 83          /// <summary>
 84          /// Initializes a new instance of the <see cref="TranslatorStubs"/> class with the specified
 85          /// <see cref="Translator"/> instance.
 86          /// </summary>
 87          /// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param>
 88          /// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
 89          public TranslatorStubs(AddressTable<ulong> functionTable)
 90          {
 91              ArgumentNullException.ThrowIfNull(functionTable);
 92  
 93              _functionTable = functionTable;
 94              _slowDispatchStub = new(GenerateSlowDispatchStub, isThreadSafe: true);
 95              _dispatchStub = new(GenerateDispatchStub, isThreadSafe: true);
 96              _dispatchLoop = new(GenerateDispatchLoop, isThreadSafe: true);
 97              _contextWrapper = new(GenerateContextWrapper, isThreadSafe: true);
 98          }
 99  
100          /// <summary>
101          /// Releases all resources used by the <see cref="TranslatorStubs"/> instance.
102          /// </summary>
103          public void Dispose()
104          {
105              Dispose(true);
106              GC.SuppressFinalize(this);
107          }
108  
109          /// <summary>
110          /// Releases all unmanaged and optionally managed resources used by the <see cref="TranslatorStubs"/> instance.
111          /// </summary>
112          /// <param name="disposing"><see langword="true"/> to dispose managed resources also; otherwise just unmanaged resouces</param>
113          protected virtual void Dispose(bool disposing)
114          {
115              if (!_disposed)
116              {
117                  if (_dispatchStub.IsValueCreated)
118                  {
119                      JitCache.Unmap(_dispatchStub.Value);
120                  }
121  
122                  if (_dispatchLoop.IsValueCreated)
123                  {
124                      JitCache.Unmap(Marshal.GetFunctionPointerForDelegate(_dispatchLoop.Value));
125                  }
126  
127                  _disposed = true;
128              }
129          }
130  
131          /// <summary>
132          /// Frees resources used by the <see cref="TranslatorStubs"/> instance.
133          /// </summary>
134          ~TranslatorStubs()
135          {
136              Dispose(false);
137          }
138  
139          /// <summary>
140          /// Generates a <see cref="DispatchStub"/>.
141          /// </summary>
142          /// <returns>Generated <see cref="DispatchStub"/></returns>
143          private IntPtr GenerateDispatchStub()
144          {
145              var context = new EmitterContext();
146  
147              Operand lblFallback = Label();
148              Operand lblEnd = Label();
149  
150              // Load the target guest address from the native context.
151              Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
152              Operand guestAddress = context.Load(OperandType.I64,
153                  context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset())));
154  
155              // Check if guest address is within range of the AddressTable.
156              Operand masked = context.BitwiseAnd(guestAddress, Const(~_functionTable.Mask));
157              context.BranchIfTrue(lblFallback, masked);
158  
159              Operand index = default;
160              Operand page = Const((long)_functionTable.Base);
161  
162              for (int i = 0; i < _functionTable.Levels.Length; i++)
163              {
164                  ref var level = ref _functionTable.Levels[i];
165  
166                  // level.Mask is not used directly because it is more often bigger than 32-bits, so it will not
167                  // be encoded as an immediate on x86's bitwise and operation.
168                  Operand mask = Const(level.Mask >> level.Index);
169  
170                  index = context.BitwiseAnd(context.ShiftRightUI(guestAddress, Const(level.Index)), mask);
171  
172                  if (i < _functionTable.Levels.Length - 1)
173                  {
174                      page = context.Load(OperandType.I64, context.Add(page, context.ShiftLeft(index, Const(3))));
175                      context.BranchIfFalse(lblFallback, page);
176                  }
177              }
178  
179              Operand hostAddress;
180              Operand hostAddressAddr = context.Add(page, context.ShiftLeft(index, Const(3)));
181              hostAddress = context.Load(OperandType.I64, hostAddressAddr);
182              context.Tailcall(hostAddress, nativeContext);
183  
184              context.MarkLabel(lblFallback);
185              hostAddress = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)), guestAddress);
186              context.Tailcall(hostAddress, nativeContext);
187  
188              var cfg = context.GetControlFlowGraph();
189              var retType = OperandType.I64;
190              var argTypes = new[] { OperandType.I64 };
191  
192              var func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<GuestFunction>();
193  
194              return Marshal.GetFunctionPointerForDelegate(func);
195          }
196  
197          /// <summary>
198          /// Generates a <see cref="SlowDispatchStub"/>.
199          /// </summary>
200          /// <returns>Generated <see cref="SlowDispatchStub"/></returns>
201          private IntPtr GenerateSlowDispatchStub()
202          {
203              var context = new EmitterContext();
204  
205              // Load the target guest address from the native context.
206              Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
207              Operand guestAddress = context.Load(OperandType.I64,
208                  context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset())));
209  
210              Operand hostAddress = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)), guestAddress);
211              context.Tailcall(hostAddress, nativeContext);
212  
213              var cfg = context.GetControlFlowGraph();
214              var retType = OperandType.I64;
215              var argTypes = new[] { OperandType.I64 };
216  
217              var func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<GuestFunction>();
218  
219              return Marshal.GetFunctionPointerForDelegate(func);
220          }
221  
222          /// <summary>
223          /// Emits code that syncs FP state before executing guest code, or returns it to normal.
224          /// </summary>
225          /// <param name="context">Emitter context for the method</param>
226          /// <param name="nativeContext">Pointer to the native context</param>
227          /// <param name="enter">True if entering guest code, false otherwise</param>
228          private static void EmitSyncFpContext(EmitterContext context, Operand nativeContext, bool enter)
229          {
230              if (enter)
231              {
232                  InstEmitSimdHelper.EnterArmFpMode(context, (flag) =>
233                  {
234                      Operand flagAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRegisterOffset(new Register((int)flag, RegisterType.FpFlag))));
235                      return context.Load(OperandType.I32, flagAddress);
236                  });
237              }
238              else
239              {
240                  InstEmitSimdHelper.ExitArmFpMode(context, (flag, value) =>
241                  {
242                      Operand flagAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRegisterOffset(new Register((int)flag, RegisterType.FpFlag))));
243                      context.Store(flagAddress, value);
244                  });
245              }
246          }
247  
248          /// <summary>
249          /// Generates a <see cref="DispatchLoop"/> function.
250          /// </summary>
251          /// <returns><see cref="DispatchLoop"/> function</returns>
252          private DispatcherFunction GenerateDispatchLoop()
253          {
254              var context = new EmitterContext();
255  
256              Operand beginLbl = Label();
257              Operand endLbl = Label();
258  
259              Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
260              Operand guestAddress = context.Copy(
261                  context.AllocateLocal(OperandType.I64),
262                  context.LoadArgument(OperandType.I64, 1));
263  
264              Operand runningAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRunningOffset()));
265              Operand dispatchAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()));
266  
267              EmitSyncFpContext(context, nativeContext, true);
268  
269              context.MarkLabel(beginLbl);
270              context.Store(dispatchAddress, guestAddress);
271              context.Copy(guestAddress, context.Call(Const((ulong)DispatchStub), OperandType.I64, nativeContext));
272              context.BranchIfFalse(endLbl, guestAddress);
273              context.BranchIfFalse(endLbl, context.Load(OperandType.I32, runningAddress));
274              context.Branch(beginLbl);
275  
276              context.MarkLabel(endLbl);
277  
278              EmitSyncFpContext(context, nativeContext, false);
279  
280              context.Return();
281  
282              var cfg = context.GetControlFlowGraph();
283              var retType = OperandType.None;
284              var argTypes = new[] { OperandType.I64, OperandType.I64 };
285  
286              return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<DispatcherFunction>();
287          }
288  
289          /// <summary>
290          /// Generates a <see cref="ContextWrapper"/> function.
291          /// </summary>
292          /// <returns><see cref="ContextWrapper"/> function</returns>
293          private WrapperFunction GenerateContextWrapper()
294          {
295              var context = new EmitterContext();
296  
297              Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
298              Operand guestMethod = context.LoadArgument(OperandType.I64, 1);
299  
300              EmitSyncFpContext(context, nativeContext, true);
301              Operand returnValue = context.Call(guestMethod, OperandType.I64, nativeContext);
302              EmitSyncFpContext(context, nativeContext, false);
303  
304              context.Return(returnValue);
305  
306              var cfg = context.GetControlFlowGraph();
307              var retType = OperandType.I64;
308              var argTypes = new[] { OperandType.I64, OperandType.I64 };
309  
310              return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<WrapperFunction>();
311          }
312      }
313  }