ILEmitterCtx.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760
  1. using ChocolArm64.Decoders;
  2. using ChocolArm64.Instructions;
  3. using ChocolArm64.State;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Reflection;
  7. using System.Reflection.Emit;
  8. namespace ChocolArm64.Translation
  9. {
  10. class ILEmitterCtx
  11. {
  12. private TranslatorCache _cache;
  13. private TranslatorQueue _queue;
  14. private Dictionary<long, ILLabel> _labels;
  15. private long _subPosition;
  16. private int _opcIndex;
  17. private Block _currBlock;
  18. public Block CurrBlock => _currBlock;
  19. public OpCode64 CurrOp => _currBlock?.OpCodes[_opcIndex];
  20. public TranslationTier Tier { get; }
  21. public Aarch32Mode Mode { get; } = Aarch32Mode.User; //TODO
  22. private Dictionary<Block, ILBlock> _visitedBlocks;
  23. private Queue<Block> _branchTargets;
  24. private List<ILBlock> _ilBlocks;
  25. private ILBlock _ilBlock;
  26. private OpCode64 _optOpLastCompare;
  27. private OpCode64 _optOpLastFlagSet;
  28. //This is the index of the temporary register, used to store temporary
  29. //values needed by some functions, since IL doesn't have a swap instruction.
  30. //You can use any value here as long it doesn't conflict with the indices
  31. //for the other registers. Any value >= 64 or < 0 will do.
  32. private const int IntTmpIndex = -1;
  33. private const int RorTmpIndex = -2;
  34. private const int CmpOptTmp1Index = -3;
  35. private const int CmpOptTmp2Index = -4;
  36. private const int VecTmp1Index = -5;
  37. private const int VecTmp2Index = -6;
  38. private const int IntTmp2Index = -7;
  39. public ILEmitterCtx(TranslatorCache cache, TranslatorQueue queue, TranslationTier tier, Block graph)
  40. {
  41. _cache = cache ?? throw new ArgumentNullException(nameof(cache));
  42. _queue = queue ?? throw new ArgumentNullException(nameof(queue));
  43. _currBlock = graph ?? throw new ArgumentNullException(nameof(graph));
  44. Tier = tier;
  45. _labels = new Dictionary<long, ILLabel>();
  46. _visitedBlocks = new Dictionary<Block, ILBlock>();
  47. _visitedBlocks.Add(graph, new ILBlock());
  48. _branchTargets = new Queue<Block>();
  49. _ilBlocks = new List<ILBlock>();
  50. _subPosition = graph.Position;
  51. ResetBlockState();
  52. AdvanceOpCode();
  53. }
  54. public ILBlock[] GetILBlocks()
  55. {
  56. EmitAllOpCodes();
  57. return _ilBlocks.ToArray();
  58. }
  59. private void EmitAllOpCodes()
  60. {
  61. do
  62. {
  63. EmitOpCode();
  64. }
  65. while (AdvanceOpCode());
  66. }
  67. private void EmitOpCode()
  68. {
  69. if (_currBlock == null)
  70. {
  71. return;
  72. }
  73. if (_opcIndex == 0)
  74. {
  75. MarkLabel(GetLabel(_currBlock.Position));
  76. EmitSynchronization();
  77. }
  78. //On AARCH32 mode, (almost) all instruction can be conditionally
  79. //executed, and the required condition is encoded on the opcode.
  80. //We handle that here, skipping the instruction if the condition
  81. //is not met. We can just ignore it when the condition is "Always",
  82. //because in this case the instruction is always going to be executed.
  83. //Condition "Never" is also ignored because this is a special encoding
  84. //used by some unconditional instructions.
  85. ILLabel lblSkip = null;
  86. if (CurrOp is OpCode32 op && op.Cond < Condition.Al)
  87. {
  88. lblSkip = new ILLabel();
  89. EmitCondBranch(lblSkip, GetInverseCond(op.Cond));
  90. }
  91. CurrOp.Emitter(this);
  92. if (lblSkip != null)
  93. {
  94. MarkLabel(lblSkip);
  95. //If this is the last op on the block, and there's no "next" block
  96. //after this one, then we have to return right now, with the address
  97. //of the next instruction to be executed (in the case that the condition
  98. //is false, and the branch was not taken, as all basic blocks should end with
  99. //some kind of branch).
  100. if (CurrOp == CurrBlock.GetLastOp() && CurrBlock.Next == null)
  101. {
  102. EmitStoreState();
  103. EmitLdc_I8(CurrOp.Position + CurrOp.OpCodeSizeInBytes);
  104. Emit(OpCodes.Ret);
  105. }
  106. }
  107. _ilBlock.Add(new ILBarrier());
  108. }
  109. private Condition GetInverseCond(Condition cond)
  110. {
  111. //Bit 0 of all conditions is basically a negation bit, so
  112. //inverting this bit has the effect of inverting the condition.
  113. return (Condition)((int)cond ^ 1);
  114. }
  115. private void EmitSynchronization()
  116. {
  117. EmitLdarg(TranslatedSub.StateArgIdx);
  118. EmitLdc_I4(_currBlock.OpCodes.Count);
  119. EmitPrivateCall(typeof(CpuThreadState), nameof(CpuThreadState.Synchronize));
  120. EmitLdc_I4(0);
  121. ILLabel lblContinue = new ILLabel();
  122. Emit(OpCodes.Bne_Un_S, lblContinue);
  123. EmitLdc_I8(0);
  124. Emit(OpCodes.Ret);
  125. MarkLabel(lblContinue);
  126. }
  127. private bool AdvanceOpCode()
  128. {
  129. if (_currBlock == null)
  130. {
  131. return false;
  132. }
  133. while (++_opcIndex >= _currBlock.OpCodes.Count)
  134. {
  135. if (!AdvanceBlock())
  136. {
  137. return false;
  138. }
  139. ResetBlockState();
  140. }
  141. return true;
  142. }
  143. private bool AdvanceBlock()
  144. {
  145. if (_currBlock.Branch != null)
  146. {
  147. if (_visitedBlocks.TryAdd(_currBlock.Branch, _ilBlock.Branch))
  148. {
  149. _branchTargets.Enqueue(_currBlock.Branch);
  150. }
  151. }
  152. if (_currBlock.Next != null)
  153. {
  154. if (_visitedBlocks.TryAdd(_currBlock.Next, _ilBlock.Next))
  155. {
  156. _currBlock = _currBlock.Next;
  157. return true;
  158. }
  159. else
  160. {
  161. Emit(OpCodes.Br, GetLabel(_currBlock.Next.Position));
  162. }
  163. }
  164. return _branchTargets.TryDequeue(out _currBlock);
  165. }
  166. private void ResetBlockState()
  167. {
  168. _ilBlock = _visitedBlocks[_currBlock];
  169. _ilBlocks.Add(_ilBlock);
  170. _ilBlock.Next = GetOrCreateILBlock(_currBlock.Next);
  171. _ilBlock.Branch = GetOrCreateILBlock(_currBlock.Branch);
  172. _opcIndex = -1;
  173. _optOpLastFlagSet = null;
  174. _optOpLastCompare = null;
  175. }
  176. private ILBlock GetOrCreateILBlock(Block block)
  177. {
  178. if (block == null)
  179. {
  180. return null;
  181. }
  182. if (_visitedBlocks.TryGetValue(block, out ILBlock ilBlock))
  183. {
  184. return ilBlock;
  185. }
  186. return new ILBlock();
  187. }
  188. public void TranslateAhead(long position, ExecutionMode mode = ExecutionMode.Aarch64)
  189. {
  190. if (_cache.TryGetSubroutine(position, out TranslatedSub sub) && sub.Tier != TranslationTier.Tier0)
  191. {
  192. return;
  193. }
  194. _queue.Enqueue(new TranslatorQueueItem(position, mode, TranslationTier.Tier1));
  195. }
  196. public bool TryOptEmitSubroutineCall()
  197. {
  198. if (_currBlock.Next == null)
  199. {
  200. return false;
  201. }
  202. if (CurrOp.Emitter != InstEmit.Bl)
  203. {
  204. return false;
  205. }
  206. if (!_cache.TryGetSubroutine(((OpCodeBImmAl64)CurrOp).Imm, out TranslatedSub subroutine))
  207. {
  208. return false;
  209. }
  210. for (int index = 0; index < TranslatedSub.FixedArgTypes.Length; index++)
  211. {
  212. EmitLdarg(index);
  213. }
  214. EmitCall(subroutine.Method);
  215. return true;
  216. }
  217. public void TryOptMarkCondWithoutCmp()
  218. {
  219. _optOpLastCompare = CurrOp;
  220. InstEmitAluHelper.EmitAluLoadOpers(this);
  221. Stloc(CmpOptTmp2Index, IoType.Int);
  222. Stloc(CmpOptTmp1Index, IoType.Int);
  223. }
  224. private Dictionary<Condition, OpCode> _branchOps = new Dictionary<Condition, OpCode>()
  225. {
  226. { Condition.Eq, OpCodes.Beq },
  227. { Condition.Ne, OpCodes.Bne_Un },
  228. { Condition.GeUn, OpCodes.Bge_Un },
  229. { Condition.LtUn, OpCodes.Blt_Un },
  230. { Condition.GtUn, OpCodes.Bgt_Un },
  231. { Condition.LeUn, OpCodes.Ble_Un },
  232. { Condition.Ge, OpCodes.Bge },
  233. { Condition.Lt, OpCodes.Blt },
  234. { Condition.Gt, OpCodes.Bgt },
  235. { Condition.Le, OpCodes.Ble }
  236. };
  237. public void EmitCondBranch(ILLabel target, Condition cond)
  238. {
  239. if (_optOpLastCompare != null &&
  240. _optOpLastCompare == _optOpLastFlagSet && _branchOps.ContainsKey(cond))
  241. {
  242. if (_optOpLastCompare.Emitter == InstEmit.Subs)
  243. {
  244. Ldloc(CmpOptTmp1Index, IoType.Int, _optOpLastCompare.RegisterSize);
  245. Ldloc(CmpOptTmp2Index, IoType.Int, _optOpLastCompare.RegisterSize);
  246. Emit(_branchOps[cond], target);
  247. return;
  248. }
  249. else if (_optOpLastCompare.Emitter == InstEmit.Adds && cond != Condition.GeUn
  250. && cond != Condition.LtUn
  251. && cond != Condition.GtUn
  252. && cond != Condition.LeUn)
  253. {
  254. //There are several limitations that needs to be taken into account for CMN comparisons:
  255. //* The unsigned comparisons are not valid, as they depend on the
  256. //carry flag value, and they will have different values for addition and
  257. //subtraction. For addition, it's carry, and for subtraction, it's borrow.
  258. //So, we need to make sure we're not doing a unsigned compare for the CMN case.
  259. //* We can only do the optimization for the immediate variants,
  260. //because when the second operand value is exactly INT_MIN, we can't
  261. //negate the value as theres no positive counterpart.
  262. //Such invalid values can't be encoded on the immediate encodings.
  263. if (_optOpLastCompare is IOpCodeAluImm64 op)
  264. {
  265. Ldloc(CmpOptTmp1Index, IoType.Int, _optOpLastCompare.RegisterSize);
  266. if (_optOpLastCompare.RegisterSize == RegisterSize.Int32)
  267. {
  268. EmitLdc_I4((int)-op.Imm);
  269. }
  270. else
  271. {
  272. EmitLdc_I8(-op.Imm);
  273. }
  274. Emit(_branchOps[cond], target);
  275. return;
  276. }
  277. }
  278. }
  279. OpCode ilOp;
  280. int intCond = (int)cond;
  281. if (intCond < 14)
  282. {
  283. int condTrue = intCond >> 1;
  284. switch (condTrue)
  285. {
  286. case 0: EmitLdflg((int)PState.ZBit); break;
  287. case 1: EmitLdflg((int)PState.CBit); break;
  288. case 2: EmitLdflg((int)PState.NBit); break;
  289. case 3: EmitLdflg((int)PState.VBit); break;
  290. case 4:
  291. EmitLdflg((int)PState.CBit);
  292. EmitLdflg((int)PState.ZBit);
  293. Emit(OpCodes.Not);
  294. Emit(OpCodes.And);
  295. break;
  296. case 5:
  297. case 6:
  298. EmitLdflg((int)PState.NBit);
  299. EmitLdflg((int)PState.VBit);
  300. Emit(OpCodes.Ceq);
  301. if (condTrue == 6)
  302. {
  303. EmitLdflg((int)PState.ZBit);
  304. Emit(OpCodes.Not);
  305. Emit(OpCodes.And);
  306. }
  307. break;
  308. }
  309. ilOp = (intCond & 1) != 0
  310. ? OpCodes.Brfalse
  311. : OpCodes.Brtrue;
  312. }
  313. else
  314. {
  315. ilOp = OpCodes.Br;
  316. }
  317. Emit(ilOp, target);
  318. }
  319. public void EmitCast(IntType intType)
  320. {
  321. switch (intType)
  322. {
  323. case IntType.UInt8: Emit(OpCodes.Conv_U1); break;
  324. case IntType.UInt16: Emit(OpCodes.Conv_U2); break;
  325. case IntType.UInt32: Emit(OpCodes.Conv_U4); break;
  326. case IntType.UInt64: Emit(OpCodes.Conv_U8); break;
  327. case IntType.Int8: Emit(OpCodes.Conv_I1); break;
  328. case IntType.Int16: Emit(OpCodes.Conv_I2); break;
  329. case IntType.Int32: Emit(OpCodes.Conv_I4); break;
  330. case IntType.Int64: Emit(OpCodes.Conv_I8); break;
  331. }
  332. bool sz64 = CurrOp.RegisterSize != RegisterSize.Int32;
  333. if (sz64 == (intType == IntType.UInt64 ||
  334. intType == IntType.Int64))
  335. {
  336. return;
  337. }
  338. if (sz64)
  339. {
  340. Emit(intType >= IntType.Int8
  341. ? OpCodes.Conv_I8
  342. : OpCodes.Conv_U8);
  343. }
  344. else
  345. {
  346. Emit(OpCodes.Conv_U4);
  347. }
  348. }
  349. public void EmitLsl(int amount) => EmitILShift(amount, OpCodes.Shl);
  350. public void EmitLsr(int amount) => EmitILShift(amount, OpCodes.Shr_Un);
  351. public void EmitAsr(int amount) => EmitILShift(amount, OpCodes.Shr);
  352. private void EmitILShift(int amount, OpCode ilOp)
  353. {
  354. if (amount > 0)
  355. {
  356. EmitLdc_I4(amount);
  357. Emit(ilOp);
  358. }
  359. }
  360. public void EmitRor(int amount)
  361. {
  362. if (amount > 0)
  363. {
  364. Stloc(RorTmpIndex, IoType.Int);
  365. Ldloc(RorTmpIndex, IoType.Int);
  366. EmitLdc_I4(amount);
  367. Emit(OpCodes.Shr_Un);
  368. Ldloc(RorTmpIndex, IoType.Int);
  369. EmitLdc_I4(CurrOp.GetBitsCount() - amount);
  370. Emit(OpCodes.Shl);
  371. Emit(OpCodes.Or);
  372. }
  373. }
  374. public ILLabel GetLabel(long position)
  375. {
  376. if (!_labels.TryGetValue(position, out ILLabel output))
  377. {
  378. output = new ILLabel();
  379. _labels.Add(position, output);
  380. }
  381. return output;
  382. }
  383. public void MarkLabel(ILLabel label)
  384. {
  385. _ilBlock.Add(label);
  386. }
  387. public void Emit(OpCode ilOp)
  388. {
  389. _ilBlock.Add(new ILOpCode(ilOp));
  390. }
  391. public void Emit(OpCode ilOp, ILLabel label)
  392. {
  393. _ilBlock.Add(new ILOpCodeBranch(ilOp, label));
  394. }
  395. public void EmitFieldLoad(FieldInfo info)
  396. {
  397. _ilBlock.Add(new ILOpCodeLoadField(info));
  398. }
  399. public void EmitPrint(string text)
  400. {
  401. _ilBlock.Add(new ILOpCodeLog(text));
  402. }
  403. public void EmitLdarg(int index)
  404. {
  405. _ilBlock.Add(new ILOpCodeLoad(index, IoType.Arg));
  406. }
  407. public void EmitLdintzr(int index)
  408. {
  409. if (index != RegisterAlias.Zr)
  410. {
  411. EmitLdint(index);
  412. }
  413. else
  414. {
  415. EmitLdc_I(0);
  416. }
  417. }
  418. public void EmitStintzr(int index)
  419. {
  420. if (index != RegisterAlias.Zr)
  421. {
  422. EmitStint(index);
  423. }
  424. else
  425. {
  426. Emit(OpCodes.Pop);
  427. }
  428. }
  429. public void EmitLoadState()
  430. {
  431. if (_ilBlock.Next == null)
  432. {
  433. throw new InvalidOperationException("Can't load state for next block, because there's no next block.");
  434. }
  435. _ilBlock.Add(new ILOpCodeLoadState(_ilBlock.Next));
  436. }
  437. public void EmitStoreState()
  438. {
  439. _ilBlock.Add(new ILOpCodeStoreState(_ilBlock));
  440. }
  441. public void EmitLdtmp() => EmitLdint(IntTmpIndex);
  442. public void EmitSttmp() => EmitStint(IntTmpIndex);
  443. public void EmitLdtmp2() => EmitLdint(IntTmp2Index);
  444. public void EmitSttmp2() => EmitStint(IntTmp2Index);
  445. public void EmitLdvectmp() => EmitLdvec(VecTmp1Index);
  446. public void EmitStvectmp() => EmitStvec(VecTmp1Index);
  447. public void EmitLdvectmp2() => EmitLdvec(VecTmp2Index);
  448. public void EmitStvectmp2() => EmitStvec(VecTmp2Index);
  449. public void EmitLdint(int index) => Ldloc(index, IoType.Int);
  450. public void EmitStint(int index) => Stloc(index, IoType.Int);
  451. public void EmitLdvec(int index) => Ldloc(index, IoType.Vector);
  452. public void EmitStvec(int index) => Stloc(index, IoType.Vector);
  453. public void EmitLdflg(int index) => Ldloc(index, IoType.Flag);
  454. public void EmitStflg(int index)
  455. {
  456. //Set this only if any of the NZCV flag bits were modified.
  457. //This is used to ensure that, when emiting a direct IL branch
  458. //instruction for compare + branch sequences, we're not expecting
  459. //to use comparison values from an old instruction, when in fact
  460. //the flags were already overwritten by another instruction further along.
  461. if (index >= (int)PState.VBit)
  462. {
  463. _optOpLastFlagSet = CurrOp;
  464. }
  465. Stloc(index, IoType.Flag);
  466. }
  467. private void Ldloc(int index, IoType ioType)
  468. {
  469. _ilBlock.Add(new ILOpCodeLoad(index, ioType, CurrOp.RegisterSize));
  470. }
  471. private void Ldloc(int index, IoType ioType, RegisterSize registerSize)
  472. {
  473. _ilBlock.Add(new ILOpCodeLoad(index, ioType, registerSize));
  474. }
  475. private void Stloc(int index, IoType ioType)
  476. {
  477. _ilBlock.Add(new ILOpCodeStore(index, ioType, CurrOp.RegisterSize));
  478. }
  479. public void EmitCallPropGet(Type objType, string propName)
  480. {
  481. if (objType == null)
  482. {
  483. throw new ArgumentNullException(nameof(objType));
  484. }
  485. if (propName == null)
  486. {
  487. throw new ArgumentNullException(nameof(propName));
  488. }
  489. EmitCall(objType.GetMethod($"get_{propName}"));
  490. }
  491. public void EmitCallPropSet(Type objType, string propName)
  492. {
  493. if (objType == null)
  494. {
  495. throw new ArgumentNullException(nameof(objType));
  496. }
  497. if (propName == null)
  498. {
  499. throw new ArgumentNullException(nameof(propName));
  500. }
  501. EmitCall(objType.GetMethod($"set_{propName}"));
  502. }
  503. public void EmitCallPrivatePropGet(Type objType, string propName)
  504. {
  505. if (objType == null)
  506. {
  507. throw new ArgumentNullException(nameof(objType));
  508. }
  509. if (propName == null)
  510. {
  511. throw new ArgumentNullException(nameof(propName));
  512. }
  513. EmitPrivateCall(objType, $"get_{propName}");
  514. }
  515. public void EmitCallPrivatePropSet(Type objType, string propName)
  516. {
  517. if (objType == null)
  518. {
  519. throw new ArgumentNullException(nameof(objType));
  520. }
  521. if (propName == null)
  522. {
  523. throw new ArgumentNullException(nameof(propName));
  524. }
  525. EmitPrivateCall(objType, $"set_{propName}");
  526. }
  527. public void EmitCall(Type objType, string mthdName)
  528. {
  529. if (objType == null)
  530. {
  531. throw new ArgumentNullException(nameof(objType));
  532. }
  533. if (mthdName == null)
  534. {
  535. throw new ArgumentNullException(nameof(mthdName));
  536. }
  537. EmitCall(objType.GetMethod(mthdName));
  538. }
  539. public void EmitPrivateCall(Type objType, string mthdName)
  540. {
  541. if (objType == null)
  542. {
  543. throw new ArgumentNullException(nameof(objType));
  544. }
  545. if (mthdName == null)
  546. {
  547. throw new ArgumentNullException(nameof(mthdName));
  548. }
  549. EmitCall(objType.GetMethod(mthdName, BindingFlags.Instance | BindingFlags.NonPublic));
  550. }
  551. public void EmitCall(MethodInfo mthdInfo, bool isVirtual = false)
  552. {
  553. _ilBlock.Add(new ILOpCodeCall(mthdInfo ?? throw new ArgumentNullException(nameof(mthdInfo)), isVirtual));
  554. }
  555. public void EmitLdc_I(long value)
  556. {
  557. if (CurrOp.RegisterSize == RegisterSize.Int32)
  558. {
  559. EmitLdc_I4((int)value);
  560. }
  561. else
  562. {
  563. EmitLdc_I8(value);
  564. }
  565. }
  566. public void EmitLdc_I4(int value)
  567. {
  568. _ilBlock.Add(new ILOpCodeConst(value));
  569. }
  570. public void EmitLdc_I8(long value)
  571. {
  572. _ilBlock.Add(new ILOpCodeConst(value));
  573. }
  574. public void EmitLdc_R4(float value)
  575. {
  576. _ilBlock.Add(new ILOpCodeConst(value));
  577. }
  578. public void EmitLdc_R8(double value)
  579. {
  580. _ilBlock.Add(new ILOpCodeConst(value));
  581. }
  582. public void EmitZnFlagCheck()
  583. {
  584. EmitZnCheck(OpCodes.Ceq, (int)PState.ZBit);
  585. EmitZnCheck(OpCodes.Clt, (int)PState.NBit);
  586. }
  587. private void EmitZnCheck(OpCode ilCmpOp, int flag)
  588. {
  589. Emit(OpCodes.Dup);
  590. Emit(OpCodes.Ldc_I4_0);
  591. if (CurrOp.RegisterSize != RegisterSize.Int32)
  592. {
  593. Emit(OpCodes.Conv_I8);
  594. }
  595. Emit(ilCmpOp);
  596. EmitStflg(flag);
  597. }
  598. }
  599. }