InstEmitMemoryHelper.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  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 lblSlowPath = Label();
  108. Operand lblEnd = Label();
  109. Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: false, size);
  110. Operand value = null;
  111. switch (size)
  112. {
  113. case 0: value = context.Load8 (physAddr); break;
  114. case 1: value = context.Load16(physAddr); break;
  115. case 2: value = context.Load (OperandType.I32, physAddr); break;
  116. case 3: value = context.Load (OperandType.I64, physAddr); break;
  117. }
  118. SetInt(context, rt, value);
  119. context.Branch(lblEnd);
  120. context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold);
  121. EmitReadIntFallback(context, address, rt, size);
  122. context.MarkLabel(lblEnd);
  123. }
  124. public static Operand EmitReadIntAligned(ArmEmitterContext context, Operand address, int size)
  125. {
  126. if ((uint)size > 4)
  127. {
  128. throw new ArgumentOutOfRangeException(nameof(size));
  129. }
  130. Operand physAddr = EmitPtPointerLoad(context, address, null, write: false, size);
  131. return size switch
  132. {
  133. 0 => context.Load8(physAddr),
  134. 1 => context.Load16(physAddr),
  135. 2 => context.Load(OperandType.I32, physAddr),
  136. 3 => context.Load(OperandType.I64, physAddr),
  137. _ => context.Load(OperandType.V128, physAddr)
  138. };
  139. }
  140. private static void EmitReadVector(
  141. ArmEmitterContext context,
  142. Operand address,
  143. Operand vector,
  144. int rt,
  145. int elem,
  146. int size)
  147. {
  148. Operand lblSlowPath = Label();
  149. Operand lblEnd = Label();
  150. Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: false, size);
  151. Operand value = null;
  152. switch (size)
  153. {
  154. case 0: value = context.VectorInsert8 (vector, context.Load8(physAddr), elem); break;
  155. case 1: value = context.VectorInsert16(vector, context.Load16(physAddr), elem); break;
  156. case 2: value = context.VectorInsert (vector, context.Load(OperandType.I32, physAddr), elem); break;
  157. case 3: value = context.VectorInsert (vector, context.Load(OperandType.I64, physAddr), elem); break;
  158. case 4: value = context.Load (OperandType.V128, physAddr); break;
  159. }
  160. context.Copy(GetVec(rt), value);
  161. context.Branch(lblEnd);
  162. context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold);
  163. EmitReadVectorFallback(context, address, vector, rt, elem, size);
  164. context.MarkLabel(lblEnd);
  165. }
  166. private static Operand VectorCreate(ArmEmitterContext context, Operand value)
  167. {
  168. return context.VectorInsert(context.VectorZero(), value, 0);
  169. }
  170. private static void EmitWriteInt(ArmEmitterContext context, Operand address, int rt, int size)
  171. {
  172. Operand lblSlowPath = Label();
  173. Operand lblEnd = Label();
  174. Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: true, size);
  175. Operand value = GetInt(context, rt);
  176. if (size < 3 && value.Type == OperandType.I64)
  177. {
  178. value = context.ConvertI64ToI32(value);
  179. }
  180. switch (size)
  181. {
  182. case 0: context.Store8 (physAddr, value); break;
  183. case 1: context.Store16(physAddr, value); break;
  184. case 2: context.Store (physAddr, value); break;
  185. case 3: context.Store (physAddr, value); break;
  186. }
  187. context.Branch(lblEnd);
  188. context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold);
  189. EmitWriteIntFallback(context, address, rt, size);
  190. context.MarkLabel(lblEnd);
  191. }
  192. public static void EmitWriteIntAligned(ArmEmitterContext context, Operand address, Operand value, int size)
  193. {
  194. if ((uint)size > 4)
  195. {
  196. throw new ArgumentOutOfRangeException(nameof(size));
  197. }
  198. Operand physAddr = EmitPtPointerLoad(context, address, null, write: true, size);
  199. if (size < 3 && value.Type == OperandType.I64)
  200. {
  201. value = context.ConvertI64ToI32(value);
  202. }
  203. if (size == 0)
  204. {
  205. context.Store8(physAddr, value);
  206. }
  207. else if (size == 1)
  208. {
  209. context.Store16(physAddr, value);
  210. }
  211. else
  212. {
  213. context.Store(physAddr, value);
  214. }
  215. }
  216. private static void EmitWriteVector(
  217. ArmEmitterContext context,
  218. Operand address,
  219. int rt,
  220. int elem,
  221. int size)
  222. {
  223. Operand lblSlowPath = Label();
  224. Operand lblEnd = Label();
  225. Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: true, size);
  226. Operand value = GetVec(rt);
  227. switch (size)
  228. {
  229. case 0: context.Store8 (physAddr, context.VectorExtract8(value, elem)); break;
  230. case 1: context.Store16(physAddr, context.VectorExtract16(value, elem)); break;
  231. case 2: context.Store (physAddr, context.VectorExtract(OperandType.I32, value, elem)); break;
  232. case 3: context.Store (physAddr, context.VectorExtract(OperandType.I64, value, elem)); break;
  233. case 4: context.Store (physAddr, value); break;
  234. }
  235. context.Branch(lblEnd);
  236. context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold);
  237. EmitWriteVectorFallback(context, address, rt, elem, size);
  238. context.MarkLabel(lblEnd);
  239. }
  240. public static Operand EmitPtPointerLoad(ArmEmitterContext context, Operand address, Operand lblSlowPath, bool write, int size)
  241. {
  242. int ptLevelBits = context.Memory.AddressSpaceBits - PageBits;
  243. int ptLevelSize = 1 << ptLevelBits;
  244. int ptLevelMask = ptLevelSize - 1;
  245. Operand addrRotated = size != 0 ? context.RotateRight(address, Const(size)) : address;
  246. Operand addrShifted = context.ShiftRightUI(addrRotated, Const(PageBits - size));
  247. Operand pte = Ptc.State == PtcState.Disabled
  248. ? Const(context.Memory.PageTablePointer.ToInt64())
  249. : Const(context.Memory.PageTablePointer.ToInt64(), true, Ptc.PageTablePointerIndex);
  250. Operand pteOffset = context.BitwiseAnd(addrShifted, Const(addrShifted.Type, ptLevelMask));
  251. if (pteOffset.Type == OperandType.I32)
  252. {
  253. pteOffset = context.ZeroExtend32(OperandType.I64, pteOffset);
  254. }
  255. pte = context.Load(OperandType.I64, context.Add(pte, context.ShiftLeft(pteOffset, Const(3))));
  256. if (addrShifted.Type == OperandType.I32)
  257. {
  258. addrShifted = context.ZeroExtend32(OperandType.I64, addrShifted);
  259. }
  260. // If the VA is out of range, or not aligned to the access size, force PTE to 0 by masking it.
  261. pte = context.BitwiseAnd(pte, context.ShiftRightSI(context.Add(addrShifted, Const(-(long)ptLevelSize)), Const(63)));
  262. if (lblSlowPath != null)
  263. {
  264. if (write)
  265. {
  266. context.BranchIf(lblSlowPath, pte, Const(0L), Comparison.LessOrEqual);
  267. pte = context.BitwiseAnd(pte, Const(0xffffffffffffUL)); // Ignore any software protection bits. (they are still used by C# memory access)
  268. }
  269. else
  270. {
  271. pte = context.ShiftLeft(pte, Const(1));
  272. context.BranchIf(lblSlowPath, pte, Const(0L), Comparison.LessOrEqual);
  273. pte = context.ShiftRightUI(pte, Const(1));
  274. }
  275. }
  276. else
  277. {
  278. // When no label is provided to jump to a slow path if the address is invalid,
  279. // we do the validation ourselves, and throw if needed.
  280. Operand lblNotWatched = Label();
  281. // Is the page currently being tracked for read/write? If so we need to call SignalMemoryTracking.
  282. context.BranchIf(lblNotWatched, pte, Const(0L), Comparison.GreaterOrEqual, BasicBlockFrequency.Cold);
  283. // Signal memory tracking. Size here doesn't matter as address is assumed to be size aligned here.
  284. context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SignalMemoryTracking)), address, Const(1UL), Const(write ? 1 : 0));
  285. context.MarkLabel(lblNotWatched);
  286. pte = context.BitwiseAnd(pte, Const(0xffffffffffffUL)); // Ignore any software protection bits. (they are still used by C# memory access)
  287. Operand lblNonNull = Label();
  288. // Skip exception if the PTE address is non-null (not zero).
  289. context.BranchIfTrue(lblNonNull, pte, BasicBlockFrequency.Cold);
  290. // The call is not expected to return (it should throw).
  291. context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ThrowInvalidMemoryAccess)), address);
  292. context.MarkLabel(lblNonNull);
  293. }
  294. Operand pageOffset = context.BitwiseAnd(address, Const(address.Type, PageMask));
  295. if (pageOffset.Type == OperandType.I32)
  296. {
  297. pageOffset = context.ZeroExtend32(OperandType.I64, pageOffset);
  298. }
  299. return context.Add(pte, pageOffset);
  300. }
  301. private static void EmitReadIntFallback(ArmEmitterContext context, Operand address, int rt, int size)
  302. {
  303. MethodInfo info = null;
  304. switch (size)
  305. {
  306. case 0: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadByte)); break;
  307. case 1: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16)); break;
  308. case 2: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32)); break;
  309. case 3: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64)); break;
  310. }
  311. SetInt(context, rt, context.Call(info, address));
  312. }
  313. private static void EmitReadVectorFallback(
  314. ArmEmitterContext context,
  315. Operand address,
  316. Operand vector,
  317. int rt,
  318. int elem,
  319. int size)
  320. {
  321. MethodInfo info = null;
  322. switch (size)
  323. {
  324. case 0: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadByte)); break;
  325. case 1: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16)); break;
  326. case 2: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32)); break;
  327. case 3: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64)); break;
  328. case 4: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadVector128)); break;
  329. }
  330. Operand value = context.Call(info, address);
  331. switch (size)
  332. {
  333. case 0: value = context.VectorInsert8 (vector, value, elem); break;
  334. case 1: value = context.VectorInsert16(vector, value, elem); break;
  335. case 2: value = context.VectorInsert (vector, value, elem); break;
  336. case 3: value = context.VectorInsert (vector, value, elem); break;
  337. }
  338. context.Copy(GetVec(rt), value);
  339. }
  340. private static void EmitWriteIntFallback(ArmEmitterContext context, Operand address, int rt, int size)
  341. {
  342. MethodInfo info = null;
  343. switch (size)
  344. {
  345. case 0: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteByte)); break;
  346. case 1: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt16)); break;
  347. case 2: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt32)); break;
  348. case 3: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt64)); break;
  349. }
  350. Operand value = GetInt(context, rt);
  351. if (size < 3 && value.Type == OperandType.I64)
  352. {
  353. value = context.ConvertI64ToI32(value);
  354. }
  355. context.Call(info, address, value);
  356. }
  357. private static void EmitWriteVectorFallback(
  358. ArmEmitterContext context,
  359. Operand address,
  360. int rt,
  361. int elem,
  362. int size)
  363. {
  364. MethodInfo info = null;
  365. switch (size)
  366. {
  367. case 0: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteByte)); break;
  368. case 1: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt16)); break;
  369. case 2: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt32)); break;
  370. case 3: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt64)); break;
  371. case 4: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteVector128)); break;
  372. }
  373. Operand value = null;
  374. if (size < 4)
  375. {
  376. switch (size)
  377. {
  378. case 0: value = context.VectorExtract8 (GetVec(rt), elem); break;
  379. case 1: value = context.VectorExtract16(GetVec(rt), elem); break;
  380. case 2: value = context.VectorExtract (OperandType.I32, GetVec(rt), elem); break;
  381. case 3: value = context.VectorExtract (OperandType.I64, GetVec(rt), elem); break;
  382. }
  383. }
  384. else
  385. {
  386. value = GetVec(rt);
  387. }
  388. context.Call(info, address, value);
  389. }
  390. private static Operand GetInt(ArmEmitterContext context, int rt)
  391. {
  392. return context.CurrOp is OpCode32 ? GetIntA32(context, rt) : GetIntOrZR(context, rt);
  393. }
  394. private static void SetInt(ArmEmitterContext context, int rt, Operand value)
  395. {
  396. if (context.CurrOp is OpCode32)
  397. {
  398. SetIntA32(context, rt, value);
  399. }
  400. else
  401. {
  402. SetIntOrZR(context, rt, value);
  403. }
  404. }
  405. // ARM32 helpers.
  406. public static Operand GetMemM(ArmEmitterContext context, bool setCarry = true)
  407. {
  408. switch (context.CurrOp)
  409. {
  410. case OpCode32MemRsImm op: return GetMShiftedByImmediate(context, op, setCarry);
  411. case OpCode32MemReg op: return GetIntA32(context, op.Rm);
  412. case OpCode32Mem op: return Const(op.Immediate);
  413. case OpCode32SimdMemImm op: return Const(op.Immediate);
  414. default: throw InvalidOpCodeType(context.CurrOp);
  415. }
  416. }
  417. private static Exception InvalidOpCodeType(OpCode opCode)
  418. {
  419. return new InvalidOperationException($"Invalid OpCode type \"{opCode?.GetType().Name ?? "null"}\".");
  420. }
  421. public static Operand GetMShiftedByImmediate(ArmEmitterContext context, OpCode32MemRsImm op, bool setCarry)
  422. {
  423. Operand m = GetIntA32(context, op.Rm);
  424. int shift = op.Immediate;
  425. if (shift == 0)
  426. {
  427. switch (op.ShiftType)
  428. {
  429. case ShiftType.Lsr: shift = 32; break;
  430. case ShiftType.Asr: shift = 32; break;
  431. case ShiftType.Ror: shift = 1; break;
  432. }
  433. }
  434. if (shift != 0)
  435. {
  436. setCarry &= false;
  437. switch (op.ShiftType)
  438. {
  439. case ShiftType.Lsl: m = InstEmitAluHelper.GetLslC(context, m, setCarry, shift); break;
  440. case ShiftType.Lsr: m = InstEmitAluHelper.GetLsrC(context, m, setCarry, shift); break;
  441. case ShiftType.Asr: m = InstEmitAluHelper.GetAsrC(context, m, setCarry, shift); break;
  442. case ShiftType.Ror:
  443. if (op.Immediate != 0)
  444. {
  445. m = InstEmitAluHelper.GetRorC(context, m, setCarry, shift);
  446. }
  447. else
  448. {
  449. m = InstEmitAluHelper.GetRrxC(context, m, setCarry);
  450. }
  451. break;
  452. }
  453. }
  454. return m;
  455. }
  456. }
  457. }