InstEmitMemoryHelper.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. using ARMeilleure.Decoders;
  2. using ARMeilleure.IntermediateRepresentation;
  3. using ARMeilleure.Memory;
  4. using ARMeilleure.Translation;
  5. using ARMeilleure.Translation.PTC;
  6. using System;
  7. using System.Reflection;
  8. using static ARMeilleure.Instructions.InstEmitHelper;
  9. using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
  10. namespace ARMeilleure.Instructions
  11. {
  12. static class InstEmitMemoryHelper
  13. {
  14. private const int PageBits = 12;
  15. private const int PageMask = (1 << PageBits) - 1;
  16. private enum Extension
  17. {
  18. Zx,
  19. Sx32,
  20. Sx64
  21. }
  22. public static void EmitLoadZx(ArmEmitterContext context, Operand address, int rt, int size)
  23. {
  24. EmitLoad(context, address, Extension.Zx, rt, size);
  25. }
  26. public static void EmitLoadSx32(ArmEmitterContext context, Operand address, int rt, int size)
  27. {
  28. EmitLoad(context, address, Extension.Sx32, rt, size);
  29. }
  30. public static void EmitLoadSx64(ArmEmitterContext context, Operand address, int rt, int size)
  31. {
  32. EmitLoad(context, address, Extension.Sx64, rt, size);
  33. }
  34. private static void EmitLoad(ArmEmitterContext context, Operand address, Extension ext, int rt, int size)
  35. {
  36. bool isSimd = IsSimd(context);
  37. if ((uint)size > (isSimd ? 4 : 3))
  38. {
  39. throw new ArgumentOutOfRangeException(nameof(size));
  40. }
  41. if (isSimd)
  42. {
  43. EmitReadVector(context, address, context.VectorZero(), rt, 0, size);
  44. }
  45. else
  46. {
  47. EmitReadInt(context, address, rt, size);
  48. }
  49. if (!isSimd && !(context.CurrOp is OpCode32 && rt == State.RegisterAlias.Aarch32Pc))
  50. {
  51. Operand value = GetInt(context, rt);
  52. if (ext == Extension.Sx32 || ext == Extension.Sx64)
  53. {
  54. OperandType destType = ext == Extension.Sx64 ? OperandType.I64 : OperandType.I32;
  55. switch (size)
  56. {
  57. case 0: value = context.SignExtend8 (destType, value); break;
  58. case 1: value = context.SignExtend16(destType, value); break;
  59. case 2: value = context.SignExtend32(destType, value); break;
  60. }
  61. }
  62. SetInt(context, rt, value);
  63. }
  64. }
  65. public static void EmitLoadSimd(
  66. ArmEmitterContext context,
  67. Operand address,
  68. Operand vector,
  69. int rt,
  70. int elem,
  71. int size)
  72. {
  73. EmitReadVector(context, address, vector, rt, elem, size);
  74. }
  75. public static void EmitStore(ArmEmitterContext context, Operand address, int rt, int size)
  76. {
  77. bool isSimd = IsSimd(context);
  78. if ((uint)size > (isSimd ? 4 : 3))
  79. {
  80. throw new ArgumentOutOfRangeException(nameof(size));
  81. }
  82. if (isSimd)
  83. {
  84. EmitWriteVector(context, address, rt, 0, size);
  85. }
  86. else
  87. {
  88. EmitWriteInt(context, address, rt, size);
  89. }
  90. }
  91. public static void EmitStoreSimd(
  92. ArmEmitterContext context,
  93. Operand address,
  94. int rt,
  95. int elem,
  96. int size)
  97. {
  98. EmitWriteVector(context, address, rt, elem, size);
  99. }
  100. private static bool IsSimd(ArmEmitterContext context)
  101. {
  102. return context.CurrOp is IOpCodeSimd &&
  103. !(context.CurrOp is OpCodeSimdMemMs ||
  104. context.CurrOp is OpCodeSimdMemSs);
  105. }
  106. public static Operand EmitReadInt(ArmEmitterContext context, Operand address, int size)
  107. {
  108. Operand temp = context.AllocateLocal(size == 3 ? OperandType.I64 : OperandType.I32);
  109. Operand lblSlowPath = Label();
  110. Operand lblEnd = Label();
  111. Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: false, size);
  112. Operand value = default;
  113. switch (size)
  114. {
  115. case 0: value = context.Load8 (physAddr); break;
  116. case 1: value = context.Load16(physAddr); break;
  117. case 2: value = context.Load (OperandType.I32, physAddr); break;
  118. case 3: value = context.Load (OperandType.I64, physAddr); break;
  119. }
  120. context.Copy(temp, value);
  121. if (!context.Memory.Type.IsHostMapped())
  122. {
  123. context.Branch(lblEnd);
  124. context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold);
  125. context.Copy(temp, EmitReadIntFallback(context, address, size));
  126. context.MarkLabel(lblEnd);
  127. }
  128. return temp;
  129. }
  130. private static void EmitReadInt(ArmEmitterContext context, Operand address, int rt, int size)
  131. {
  132. Operand lblSlowPath = Label();
  133. Operand lblEnd = Label();
  134. Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: false, size);
  135. Operand value = default;
  136. switch (size)
  137. {
  138. case 0: value = context.Load8 (physAddr); break;
  139. case 1: value = context.Load16(physAddr); break;
  140. case 2: value = context.Load (OperandType.I32, physAddr); break;
  141. case 3: value = context.Load (OperandType.I64, physAddr); break;
  142. }
  143. SetInt(context, rt, value);
  144. if (!context.Memory.Type.IsHostMapped())
  145. {
  146. context.Branch(lblEnd);
  147. context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold);
  148. EmitReadIntFallback(context, address, rt, size);
  149. context.MarkLabel(lblEnd);
  150. }
  151. }
  152. public static Operand EmitReadIntAligned(ArmEmitterContext context, Operand address, int size)
  153. {
  154. if ((uint)size > 4)
  155. {
  156. throw new ArgumentOutOfRangeException(nameof(size));
  157. }
  158. Operand physAddr = EmitPtPointerLoad(context, address, default, write: false, size);
  159. return size switch
  160. {
  161. 0 => context.Load8(physAddr),
  162. 1 => context.Load16(physAddr),
  163. 2 => context.Load(OperandType.I32, physAddr),
  164. 3 => context.Load(OperandType.I64, physAddr),
  165. _ => context.Load(OperandType.V128, physAddr)
  166. };
  167. }
  168. private static void EmitReadVector(
  169. ArmEmitterContext context,
  170. Operand address,
  171. Operand vector,
  172. int rt,
  173. int elem,
  174. int size)
  175. {
  176. Operand lblSlowPath = Label();
  177. Operand lblEnd = Label();
  178. Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: false, size);
  179. Operand value = default;
  180. switch (size)
  181. {
  182. case 0: value = context.VectorInsert8 (vector, context.Load8(physAddr), elem); break;
  183. case 1: value = context.VectorInsert16(vector, context.Load16(physAddr), elem); break;
  184. case 2: value = context.VectorInsert (vector, context.Load(OperandType.I32, physAddr), elem); break;
  185. case 3: value = context.VectorInsert (vector, context.Load(OperandType.I64, physAddr), elem); break;
  186. case 4: value = context.Load (OperandType.V128, physAddr); break;
  187. }
  188. context.Copy(GetVec(rt), value);
  189. if (!context.Memory.Type.IsHostMapped())
  190. {
  191. context.Branch(lblEnd);
  192. context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold);
  193. EmitReadVectorFallback(context, address, vector, rt, elem, size);
  194. context.MarkLabel(lblEnd);
  195. }
  196. }
  197. private static Operand VectorCreate(ArmEmitterContext context, Operand value)
  198. {
  199. return context.VectorInsert(context.VectorZero(), value, 0);
  200. }
  201. private static void EmitWriteInt(ArmEmitterContext context, Operand address, int rt, int size)
  202. {
  203. Operand lblSlowPath = Label();
  204. Operand lblEnd = Label();
  205. Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: true, size);
  206. Operand value = GetInt(context, rt);
  207. if (size < 3 && value.Type == OperandType.I64)
  208. {
  209. value = context.ConvertI64ToI32(value);
  210. }
  211. switch (size)
  212. {
  213. case 0: context.Store8 (physAddr, value); break;
  214. case 1: context.Store16(physAddr, value); break;
  215. case 2: context.Store (physAddr, value); break;
  216. case 3: context.Store (physAddr, value); break;
  217. }
  218. if (!context.Memory.Type.IsHostMapped())
  219. {
  220. context.Branch(lblEnd);
  221. context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold);
  222. EmitWriteIntFallback(context, address, rt, size);
  223. context.MarkLabel(lblEnd);
  224. }
  225. }
  226. public static void EmitWriteIntAligned(ArmEmitterContext context, Operand address, Operand value, int size)
  227. {
  228. if ((uint)size > 4)
  229. {
  230. throw new ArgumentOutOfRangeException(nameof(size));
  231. }
  232. Operand physAddr = EmitPtPointerLoad(context, address, default, write: true, size);
  233. if (size < 3 && value.Type == OperandType.I64)
  234. {
  235. value = context.ConvertI64ToI32(value);
  236. }
  237. if (size == 0)
  238. {
  239. context.Store8(physAddr, value);
  240. }
  241. else if (size == 1)
  242. {
  243. context.Store16(physAddr, value);
  244. }
  245. else
  246. {
  247. context.Store(physAddr, value);
  248. }
  249. }
  250. private static void EmitWriteVector(
  251. ArmEmitterContext context,
  252. Operand address,
  253. int rt,
  254. int elem,
  255. int size)
  256. {
  257. Operand lblSlowPath = Label();
  258. Operand lblEnd = Label();
  259. Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: true, size);
  260. Operand value = GetVec(rt);
  261. switch (size)
  262. {
  263. case 0: context.Store8 (physAddr, context.VectorExtract8(value, elem)); break;
  264. case 1: context.Store16(physAddr, context.VectorExtract16(value, elem)); break;
  265. case 2: context.Store (physAddr, context.VectorExtract(OperandType.I32, value, elem)); break;
  266. case 3: context.Store (physAddr, context.VectorExtract(OperandType.I64, value, elem)); break;
  267. case 4: context.Store (physAddr, value); break;
  268. }
  269. if (!context.Memory.Type.IsHostMapped())
  270. {
  271. context.Branch(lblEnd);
  272. context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold);
  273. EmitWriteVectorFallback(context, address, rt, elem, size);
  274. context.MarkLabel(lblEnd);
  275. }
  276. }
  277. public static Operand EmitPtPointerLoad(ArmEmitterContext context, Operand address, Operand lblSlowPath, bool write, int size)
  278. {
  279. if (context.Memory.Type.IsHostMapped())
  280. {
  281. return EmitHostMappedPointer(context, address);
  282. }
  283. int ptLevelBits = context.Memory.AddressSpaceBits - PageBits;
  284. int ptLevelSize = 1 << ptLevelBits;
  285. int ptLevelMask = ptLevelSize - 1;
  286. Operand addrRotated = size != 0 ? context.RotateRight(address, Const(size)) : address;
  287. Operand addrShifted = context.ShiftRightUI(addrRotated, Const(PageBits - size));
  288. Operand pte = !context.HasPtc
  289. ? Const(context.Memory.PageTablePointer.ToInt64())
  290. : Const(context.Memory.PageTablePointer.ToInt64(), Ptc.PageTableSymbol);
  291. Operand pteOffset = context.BitwiseAnd(addrShifted, Const(addrShifted.Type, ptLevelMask));
  292. if (pteOffset.Type == OperandType.I32)
  293. {
  294. pteOffset = context.ZeroExtend32(OperandType.I64, pteOffset);
  295. }
  296. pte = context.Load(OperandType.I64, context.Add(pte, context.ShiftLeft(pteOffset, Const(3))));
  297. if (addrShifted.Type == OperandType.I32)
  298. {
  299. addrShifted = context.ZeroExtend32(OperandType.I64, addrShifted);
  300. }
  301. // If the VA is out of range, or not aligned to the access size, force PTE to 0 by masking it.
  302. pte = context.BitwiseAnd(pte, context.ShiftRightSI(context.Add(addrShifted, Const(-(long)ptLevelSize)), Const(63)));
  303. if (lblSlowPath != default)
  304. {
  305. if (write)
  306. {
  307. context.BranchIf(lblSlowPath, pte, Const(0L), Comparison.LessOrEqual);
  308. pte = context.BitwiseAnd(pte, Const(0xffffffffffffUL)); // Ignore any software protection bits. (they are still used by C# memory access)
  309. }
  310. else
  311. {
  312. pte = context.ShiftLeft(pte, Const(1));
  313. context.BranchIf(lblSlowPath, pte, Const(0L), Comparison.LessOrEqual);
  314. pte = context.ShiftRightUI(pte, Const(1));
  315. }
  316. }
  317. else
  318. {
  319. // When no label is provided to jump to a slow path if the address is invalid,
  320. // we do the validation ourselves, and throw if needed.
  321. Operand lblNotWatched = Label();
  322. // Is the page currently being tracked for read/write? If so we need to call SignalMemoryTracking.
  323. context.BranchIf(lblNotWatched, pte, Const(0L), Comparison.GreaterOrEqual, BasicBlockFrequency.Cold);
  324. // Signal memory tracking. Size here doesn't matter as address is assumed to be size aligned here.
  325. context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SignalMemoryTracking)), address, Const(1UL), Const(write ? 1 : 0));
  326. context.MarkLabel(lblNotWatched);
  327. pte = context.BitwiseAnd(pte, Const(0xffffffffffffUL)); // Ignore any software protection bits. (they are still used by C# memory access)
  328. Operand lblNonNull = Label();
  329. // Skip exception if the PTE address is non-null (not zero).
  330. context.BranchIfTrue(lblNonNull, pte, BasicBlockFrequency.Cold);
  331. // The call is not expected to return (it should throw).
  332. context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ThrowInvalidMemoryAccess)), address);
  333. context.MarkLabel(lblNonNull);
  334. }
  335. Operand pageOffset = context.BitwiseAnd(address, Const(address.Type, PageMask));
  336. if (pageOffset.Type == OperandType.I32)
  337. {
  338. pageOffset = context.ZeroExtend32(OperandType.I64, pageOffset);
  339. }
  340. return context.Add(pte, pageOffset);
  341. }
  342. public static Operand EmitHostMappedPointer(ArmEmitterContext context, Operand address)
  343. {
  344. if (address.Type == OperandType.I32)
  345. {
  346. address = context.ZeroExtend32(OperandType.I64, address);
  347. }
  348. if (context.Memory.Type == MemoryManagerType.HostMapped)
  349. {
  350. Operand mask = Const(ulong.MaxValue >> (64 - context.Memory.AddressSpaceBits));
  351. address = context.BitwiseAnd(address, mask);
  352. }
  353. Operand baseAddr = !context.HasPtc
  354. ? Const(context.Memory.PageTablePointer.ToInt64())
  355. : Const(context.Memory.PageTablePointer.ToInt64(), Ptc.PageTableSymbol);
  356. return context.Add(baseAddr, address);
  357. }
  358. private static void EmitReadIntFallback(ArmEmitterContext context, Operand address, int rt, int size)
  359. {
  360. SetInt(context, rt, EmitReadIntFallback(context, address, size));
  361. }
  362. private static Operand EmitReadIntFallback(ArmEmitterContext context, Operand address, int size)
  363. {
  364. MethodInfo info = null;
  365. switch (size)
  366. {
  367. case 0: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadByte)); break;
  368. case 1: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16)); break;
  369. case 2: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32)); break;
  370. case 3: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64)); break;
  371. }
  372. return context.Call(info, address);
  373. }
  374. private static void EmitReadVectorFallback(
  375. ArmEmitterContext context,
  376. Operand address,
  377. Operand vector,
  378. int rt,
  379. int elem,
  380. int size)
  381. {
  382. MethodInfo info = null;
  383. switch (size)
  384. {
  385. case 0: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadByte)); break;
  386. case 1: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16)); break;
  387. case 2: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32)); break;
  388. case 3: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64)); break;
  389. case 4: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadVector128)); break;
  390. }
  391. Operand value = context.Call(info, address);
  392. switch (size)
  393. {
  394. case 0: value = context.VectorInsert8 (vector, value, elem); break;
  395. case 1: value = context.VectorInsert16(vector, value, elem); break;
  396. case 2: value = context.VectorInsert (vector, value, elem); break;
  397. case 3: value = context.VectorInsert (vector, value, elem); break;
  398. }
  399. context.Copy(GetVec(rt), value);
  400. }
  401. private static void EmitWriteIntFallback(ArmEmitterContext context, Operand address, int rt, int size)
  402. {
  403. MethodInfo info = null;
  404. switch (size)
  405. {
  406. case 0: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteByte)); break;
  407. case 1: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt16)); break;
  408. case 2: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt32)); break;
  409. case 3: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt64)); break;
  410. }
  411. Operand value = GetInt(context, rt);
  412. if (size < 3 && value.Type == OperandType.I64)
  413. {
  414. value = context.ConvertI64ToI32(value);
  415. }
  416. context.Call(info, address, value);
  417. }
  418. private static void EmitWriteVectorFallback(
  419. ArmEmitterContext context,
  420. Operand address,
  421. int rt,
  422. int elem,
  423. int size)
  424. {
  425. MethodInfo info = null;
  426. switch (size)
  427. {
  428. case 0: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteByte)); break;
  429. case 1: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt16)); break;
  430. case 2: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt32)); break;
  431. case 3: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt64)); break;
  432. case 4: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteVector128)); break;
  433. }
  434. Operand value = default;
  435. if (size < 4)
  436. {
  437. switch (size)
  438. {
  439. case 0: value = context.VectorExtract8 (GetVec(rt), elem); break;
  440. case 1: value = context.VectorExtract16(GetVec(rt), elem); break;
  441. case 2: value = context.VectorExtract (OperandType.I32, GetVec(rt), elem); break;
  442. case 3: value = context.VectorExtract (OperandType.I64, GetVec(rt), elem); break;
  443. }
  444. }
  445. else
  446. {
  447. value = GetVec(rt);
  448. }
  449. context.Call(info, address, value);
  450. }
  451. private static Operand GetInt(ArmEmitterContext context, int rt)
  452. {
  453. return context.CurrOp is OpCode32 ? GetIntA32(context, rt) : GetIntOrZR(context, rt);
  454. }
  455. private static void SetInt(ArmEmitterContext context, int rt, Operand value)
  456. {
  457. if (context.CurrOp is OpCode32)
  458. {
  459. SetIntA32(context, rt, value);
  460. }
  461. else
  462. {
  463. SetIntOrZR(context, rt, value);
  464. }
  465. }
  466. // ARM32 helpers.
  467. public static Operand GetMemM(ArmEmitterContext context, bool setCarry = true)
  468. {
  469. switch (context.CurrOp)
  470. {
  471. case IOpCode32MemRsImm op: return GetMShiftedByImmediate(context, op, setCarry);
  472. case IOpCode32MemReg op: return GetIntA32(context, op.Rm);
  473. case IOpCode32Mem op: return Const(op.Immediate);
  474. case OpCode32SimdMemImm op: return Const(op.Immediate);
  475. default: throw InvalidOpCodeType(context.CurrOp);
  476. }
  477. }
  478. private static Exception InvalidOpCodeType(OpCode opCode)
  479. {
  480. return new InvalidOperationException($"Invalid OpCode type \"{opCode?.GetType().Name ?? "null"}\".");
  481. }
  482. public static Operand GetMShiftedByImmediate(ArmEmitterContext context, IOpCode32MemRsImm op, bool setCarry)
  483. {
  484. Operand m = GetIntA32(context, op.Rm);
  485. int shift = op.Immediate;
  486. if (shift == 0)
  487. {
  488. switch (op.ShiftType)
  489. {
  490. case ShiftType.Lsr: shift = 32; break;
  491. case ShiftType.Asr: shift = 32; break;
  492. case ShiftType.Ror: shift = 1; break;
  493. }
  494. }
  495. if (shift != 0)
  496. {
  497. setCarry &= false;
  498. switch (op.ShiftType)
  499. {
  500. case ShiftType.Lsl: m = InstEmitAluHelper.GetLslC(context, m, setCarry, shift); break;
  501. case ShiftType.Lsr: m = InstEmitAluHelper.GetLsrC(context, m, setCarry, shift); break;
  502. case ShiftType.Asr: m = InstEmitAluHelper.GetAsrC(context, m, setCarry, shift); break;
  503. case ShiftType.Ror:
  504. if (op.Immediate != 0)
  505. {
  506. m = InstEmitAluHelper.GetRorC(context, m, setCarry, shift);
  507. }
  508. else
  509. {
  510. m = InstEmitAluHelper.GetRrxC(context, m, setCarry);
  511. }
  512. break;
  513. }
  514. }
  515. return m;
  516. }
  517. }
  518. }