InstEmitMemoryHelper.cs 23 KB

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