You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

569 lines
23 KiB

  1. // Copyright (c) 2016 Daniel Grunwald
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this
  4. // software and associated documentation files (the "Software"), to deal in the Software
  5. // without restriction, including without limitation the rights to use, copy, modify, merge,
  6. // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  7. // to whom the Software is furnished to do so, subject to the following conditions:
  8. //
  9. // The above copyright notice and this permission notice shall be included in all copies or
  10. // substantial portions of the Software.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  13. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  14. // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  15. // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  16. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  17. // DEALINGS IN THE SOFTWARE.
  18. using System;
  19. using System.Collections.Generic;
  20. using System.Diagnostics;
  21. using System.Linq;
  22. using ICSharpCode.Decompiler.IL.Transforms;
  23. using ICSharpCode.Decompiler.TypeSystem;
  24. namespace ICSharpCode.Decompiler.IL.ControlFlow
  25. {
  26. /// <summary>
  27. /// IL uses 'pinned locals' to prevent the GC from moving objects.
  28. ///
  29. /// C#:
  30. /// <code>
  31. /// fixed (int* s = &amp;arr[index]) { use(s); use(s); }
  32. /// </code>
  33. ///
  34. /// <para>Gets translated into IL:</para>
  35. /// <code>
  36. /// pinned local P : System.Int32&amp;
  37. ///
  38. /// stloc(P, ldelema(arr, index))
  39. /// call use(conv ref->i(ldloc P))
  40. /// call use(conv ref->i(ldloc P))
  41. /// stloc(P, conv i4->u(ldc.i4 0))
  42. /// </code>
  43. ///
  44. /// In C#, the only way to pin something is to use a fixed block
  45. /// (or to mess around with GCHandles).
  46. /// But fixed blocks are scoped, so we need to detect the region affected by the pin.
  47. /// To ensure we'll be able to collect all blocks in that region, we perform this transform
  48. /// early, before building any other control flow constructs that aren't as critical for correctness.
  49. ///
  50. /// This means this transform must run before LoopDetection.
  51. /// To make our detection job easier, we must run after variable inlining.
  52. /// </summary>
  53. public class DetectPinnedRegions : IILTransform
  54. {
  55. ILTransformContext context;
  56. public void Run(ILFunction function, ILTransformContext context)
  57. {
  58. this.context = context;
  59. foreach (var container in function.Descendants.OfType<BlockContainer>()) {
  60. context.CancellationToken.ThrowIfCancellationRequested();
  61. DetectNullSafeArrayToPointer(container);
  62. SplitBlocksAtWritesToPinnedLocals(container);
  63. foreach (var block in container.Blocks)
  64. CreatePinnedRegion(block);
  65. container.Blocks.RemoveAll(b => b.Instructions.Count == 0); // remove dummy blocks
  66. }
  67. // Sometimes there's leftover writes to the original pinned locals
  68. foreach (var block in function.Descendants.OfType<Block>()) {
  69. context.CancellationToken.ThrowIfCancellationRequested();
  70. for (int i = 0; i < block.Instructions.Count; i++) {
  71. var stloc = block.Instructions[i] as StLoc;
  72. if (stloc != null && stloc.Variable.Kind == VariableKind.PinnedLocal && stloc.Variable.LoadCount == 0 && stloc.Variable.AddressCount == 0) {
  73. if (SemanticHelper.IsPure(stloc.Value.Flags)) {
  74. block.Instructions.RemoveAt(i--);
  75. } else {
  76. stloc.ReplaceWith(stloc.Value);
  77. }
  78. }
  79. }
  80. }
  81. this.context = null;
  82. }
  83. /// <summary>
  84. /// Ensures that every write to a pinned local is followed by a branch instruction.
  85. /// This ensures the 'pinning region' does not involve any half blocks, which makes it easier to extract.
  86. /// </summary>
  87. void SplitBlocksAtWritesToPinnedLocals(BlockContainer container)
  88. {
  89. for (int i = 0; i < container.Blocks.Count; i++) {
  90. var block = container.Blocks[i];
  91. for (int j = 0; j < block.Instructions.Count - 1; j++) {
  92. var inst = block.Instructions[j];
  93. ILVariable v;
  94. if (inst.MatchStLoc(out v) && v.Kind == VariableKind.PinnedLocal && block.Instructions[j + 1].OpCode != OpCode.Branch) {
  95. // split block after j:
  96. context.Step("Split block after pinned local write", inst);
  97. var newBlock = new Block();
  98. for (int k = j + 1; k < block.Instructions.Count; k++) {
  99. newBlock.Instructions.Add(block.Instructions[k]);
  100. }
  101. newBlock.ILRange = newBlock.Instructions[0].ILRange;
  102. block.Instructions.RemoveRange(j + 1, newBlock.Instructions.Count);
  103. block.Instructions.Add(new Branch(newBlock));
  104. container.Blocks.Insert(i + 1, newBlock);
  105. }
  106. }
  107. }
  108. }
  109. #region null-safe array to pointer
  110. void DetectNullSafeArrayToPointer(BlockContainer container)
  111. {
  112. // Detect the following pattern:
  113. // ...
  114. // stloc V(ldloc S)
  115. // if (comp(ldloc S == ldnull)) br B_null_or_empty
  116. // br B_not_null
  117. // }
  118. // Block B_not_null {
  119. // if (conv i->i4 (ldlen(ldloc V))) br B_not_null_and_not_empty
  120. // br B_null_or_empty
  121. // }
  122. // Block B_not_null_and_not_empty {
  123. // stloc P(ldelema(ldloc V, ldc.i4 0, ...))
  124. // br B_target
  125. // }
  126. // Block B_null_or_empty {
  127. // stloc P(conv i4->u(ldc.i4 0))
  128. // br B_target
  129. // }
  130. // And convert the whole thing into:
  131. // ...
  132. // stloc P(array.to.pointer(V))
  133. // br B_target
  134. bool modified = false;
  135. for (int i = 0; i < container.Blocks.Count; i++) {
  136. var block = container.Blocks[i];
  137. ILVariable v, p;
  138. Block targetBlock;
  139. if (IsNullSafeArrayToPointerPattern(block, out v, out p, out targetBlock)) {
  140. context.Step("NullSafeArrayToPointerPattern", block);
  141. ILInstruction arrayToPointer = new ArrayToPointer(new LdLoc(v));
  142. if (p.StackType != StackType.Ref) {
  143. arrayToPointer = new Conv(arrayToPointer, p.StackType.ToPrimitiveType(), false, Sign.None);
  144. }
  145. block.Instructions[block.Instructions.Count - 2] = new StLoc(p, arrayToPointer);
  146. ((Branch)block.Instructions.Last()).TargetBlock = targetBlock;
  147. modified = true;
  148. }
  149. }
  150. if (modified) {
  151. container.Blocks.RemoveAll(b => b.IncomingEdgeCount == 0); // remove blocks made unreachable
  152. }
  153. }
  154. bool IsNullSafeArrayToPointerPattern(Block block, out ILVariable v, out ILVariable p, out Block targetBlock)
  155. {
  156. v = null;
  157. p = null;
  158. targetBlock = null;
  159. // ...
  160. // if (comp(ldloc V == ldnull)) br B_null_or_empty
  161. // br B_not_null
  162. var ifInst = block.Instructions.SecondToLastOrDefault() as IfInstruction;
  163. if (ifInst == null)
  164. return false;
  165. var condition = ifInst.Condition as Comp;
  166. if (!(condition != null && condition.Kind == ComparisonKind.Equality && condition.Left.MatchLdLoc(out v) && condition.Right.MatchLdNull()))
  167. return false;
  168. bool usingPreviousVar = false;
  169. if (v.Kind == VariableKind.StackSlot) {
  170. // If the variable is a stack slot, that might be due to an inline assignment,
  171. // so check the previous instruction:
  172. var previous = block.Instructions.ElementAtOrDefault(block.Instructions.Count - 3) as StLoc;
  173. if (previous != null && previous.Value.MatchLdLoc(v)) {
  174. // stloc V(ldloc S)
  175. // if (comp(ldloc S == ldnull)) ...
  176. v = previous.Variable;
  177. usingPreviousVar = true;
  178. }
  179. }
  180. if (!ifInst.TrueInst.MatchBranch(out Block nullOrEmptyBlock))
  181. return false;
  182. if (!ifInst.FalseInst.MatchNop())
  183. return false;
  184. if (nullOrEmptyBlock.Parent != block.Parent)
  185. return false;
  186. if (!IsNullSafeArrayToPointerNullOrEmptyBlock(nullOrEmptyBlock, out p, out targetBlock))
  187. return false;
  188. if (!(p.Kind == VariableKind.PinnedLocal || (usingPreviousVar && v.Kind == VariableKind.PinnedLocal)))
  189. return false;
  190. if (!block.Instructions.Last().MatchBranch(out Block notNullBlock))
  191. return false;
  192. if (notNullBlock.Parent != block.Parent)
  193. return false;
  194. return IsNullSafeArrayToPointerNotNullBlock(notNullBlock, v, p, nullOrEmptyBlock, targetBlock);
  195. }
  196. bool IsNullSafeArrayToPointerNotNullBlock(Block block, ILVariable v, ILVariable p, Block nullOrEmptyBlock, Block targetBlock)
  197. {
  198. // Block B_not_null {
  199. // if (conv i->i4 (ldlen(ldloc V))) br B_not_null_and_not_empty
  200. // br B_null_or_empty
  201. // }
  202. if (block.Instructions.Count != 2)
  203. return false;
  204. if (!block.Instructions[0].MatchIfInstruction(out ILInstruction condition, out ILInstruction trueInst))
  205. return false;
  206. if (!condition.UnwrapConv(ConversionKind.Truncate).MatchLdLen(StackType.I, out ILInstruction array))
  207. return false;
  208. if (!array.MatchLdLoc(v))
  209. return false;
  210. if (!trueInst.MatchBranch(out Block notNullAndNotEmptyBlock))
  211. return false;
  212. if (notNullAndNotEmptyBlock.Parent != block.Parent)
  213. return false;
  214. if (!IsNullSafeArrayToPointerNotNullAndNotEmptyBlock(notNullAndNotEmptyBlock, v, p, targetBlock))
  215. return false;
  216. return block.Instructions[1].MatchBranch(nullOrEmptyBlock);
  217. }
  218. bool IsNullSafeArrayToPointerNotNullAndNotEmptyBlock(Block block, ILVariable v, ILVariable p, Block targetBlock)
  219. {
  220. // Block B_not_null_and_not_empty {
  221. // stloc P(ldelema(ldloc V, ldc.i4 0, ...))
  222. // br B_target
  223. // }
  224. ILInstruction value;
  225. if (block.Instructions.Count != 2)
  226. return false;
  227. if (!block.Instructions[0].MatchStLoc(p, out value))
  228. return false;
  229. if (v.Kind == VariableKind.PinnedLocal) {
  230. value = value.UnwrapConv(ConversionKind.StopGCTracking);
  231. }
  232. if (!(value is LdElema ldelema))
  233. return false;
  234. if (!ldelema.Array.MatchLdLoc(v))
  235. return false;
  236. if (!ldelema.Indices.All(i => i.MatchLdcI4(0)))
  237. return false;
  238. return block.Instructions[1].MatchBranch(targetBlock);
  239. }
  240. bool IsNullSafeArrayToPointerNullOrEmptyBlock(Block block, out ILVariable p, out Block targetBlock)
  241. {
  242. p = null;
  243. targetBlock = null;
  244. // Block B_null_or_empty {
  245. // stloc P(conv i4->u(ldc.i4 0))
  246. // br B_target
  247. // }
  248. ILInstruction value;
  249. return block.Instructions.Count == 2
  250. && block.Instructions[0].MatchStLoc(out p, out value)
  251. && (p.Kind == VariableKind.PinnedLocal || p.Kind == VariableKind.Local)
  252. && IsNullOrZero(value)
  253. && block.Instructions[1].MatchBranch(out targetBlock);
  254. }
  255. #endregion
  256. #region CreatePinnedRegion
  257. bool CreatePinnedRegion(Block block)
  258. {
  259. // After SplitBlocksAtWritesToPinnedLocals(), only the second-to-last instruction in each block
  260. // can be a write to a pinned local.
  261. var stLoc = block.Instructions.SecondToLastOrDefault() as StLoc;
  262. if (stLoc == null || stLoc.Variable.Kind != VariableKind.PinnedLocal)
  263. return false;
  264. // stLoc is a store to a pinned local.
  265. if (IsNullOrZero(stLoc.Value))
  266. return false; // ignore unpin instructions
  267. // stLoc is a store that starts a new pinned region
  268. // Collect the blocks to be moved into the region:
  269. BlockContainer sourceContainer = (BlockContainer)block.Parent;
  270. int[] reachedEdgesPerBlock = new int[sourceContainer.Blocks.Count];
  271. Queue<Block> workList = new Queue<Block>();
  272. Block entryBlock = ((Branch)block.Instructions.Last()).TargetBlock;
  273. if (entryBlock.Parent != sourceContainer) {
  274. // we didn't find a single block to be added to the pinned region
  275. return false;
  276. }
  277. reachedEdgesPerBlock[entryBlock.ChildIndex]++;
  278. workList.Enqueue(entryBlock);
  279. while (workList.Count > 0) {
  280. Block workItem = workList.Dequeue();
  281. StLoc workStLoc = workItem.Instructions.SecondToLastOrDefault() as StLoc;
  282. int instructionCount;
  283. if (workStLoc != null && workStLoc.Variable == stLoc.Variable && IsNullOrZero(workStLoc.Value)) {
  284. // found unpin instruction: only consider branches prior to that instruction
  285. instructionCount = workStLoc.ChildIndex;
  286. } else {
  287. instructionCount = workItem.Instructions.Count;
  288. }
  289. for (int i = 0; i < instructionCount; i++) {
  290. foreach (var branch in workItem.Instructions[i].Descendants.OfType<Branch>()) {
  291. if (branch.TargetBlock.Parent == sourceContainer) {
  292. Debug.Assert(branch.TargetBlock != block);
  293. if (reachedEdgesPerBlock[branch.TargetBlock.ChildIndex]++ == 0) {
  294. // detected first edge to that block: add block as work item
  295. workList.Enqueue(branch.TargetBlock);
  296. }
  297. }
  298. }
  299. }
  300. }
  301. // Validate that all uses of a block consistently are inside or outside the pinned region.
  302. // (we cannot do this anymore after we start moving blocks around)
  303. for (int i = 0; i < sourceContainer.Blocks.Count; i++) {
  304. if (reachedEdgesPerBlock[i] != 0 && reachedEdgesPerBlock[i] != sourceContainer.Blocks[i].IncomingEdgeCount) {
  305. return false;
  306. }
  307. }
  308. context.Step("CreatePinnedRegion", block);
  309. BlockContainer body = new BlockContainer();
  310. for (int i = 0; i < sourceContainer.Blocks.Count; i++) {
  311. if (reachedEdgesPerBlock[i] > 0) {
  312. var innerBlock = sourceContainer.Blocks[i];
  313. Branch br = innerBlock.Instructions.LastOrDefault() as Branch;
  314. if (br != null && br.TargetContainer == sourceContainer && reachedEdgesPerBlock[br.TargetBlock.ChildIndex] == 0) {
  315. // branch that leaves body.
  316. // Should have an instruction that resets the pin; delete that instruction:
  317. StLoc innerStLoc = innerBlock.Instructions.SecondToLastOrDefault() as StLoc;
  318. if (innerStLoc != null && innerStLoc.Variable == stLoc.Variable && IsNullOrZero(innerStLoc.Value)) {
  319. innerBlock.Instructions.RemoveAt(innerBlock.Instructions.Count - 2);
  320. }
  321. }
  322. body.Blocks.Add(innerBlock); // move block into body
  323. sourceContainer.Blocks[i] = new Block(); // replace with dummy block
  324. // we'll delete the dummy block later
  325. }
  326. }
  327. stLoc.ReplaceWith(new PinnedRegion(stLoc.Variable, stLoc.Value, body));
  328. block.Instructions.RemoveAt(block.Instructions.Count - 1); // remove branch into body
  329. ProcessPinnedRegion((PinnedRegion)block.Instructions.Last());
  330. return true;
  331. }
  332. static bool IsNullOrZero(ILInstruction inst)
  333. {
  334. while (inst is Conv conv) {
  335. inst = conv.Argument;
  336. }
  337. return inst.MatchLdcI4(0) || inst.MatchLdNull();
  338. }
  339. #endregion
  340. #region ProcessPinnedRegion
  341. /// <summary>
  342. /// After a pinned region was detected; process its body; replacing the pin variable
  343. /// with a native pointer as far as possible.
  344. /// </summary>
  345. void ProcessPinnedRegion(PinnedRegion pinnedRegion)
  346. {
  347. BlockContainer body = (BlockContainer)pinnedRegion.Body;
  348. if (pinnedRegion.Variable.Type.Kind == TypeKind.ByReference) {
  349. // C# doesn't support a "by reference" variable, so replace it with a native pointer
  350. context.Step("Replace pinned ref-local with native pointer", pinnedRegion);
  351. ILVariable oldVar = pinnedRegion.Variable;
  352. IType elementType = ((ByReferenceType)oldVar.Type).ElementType;
  353. if (elementType.Kind == TypeKind.Pointer && pinnedRegion.Init.MatchLdFlda(out _, out var field)
  354. && ((PointerType)elementType).ElementType.Equals(field.Type))
  355. {
  356. // Roslyn 2.6 (C# 7.2) uses type "int*&" for the pinned local referring to a
  357. // fixed field of type "int".
  358. // Remove the extra level of indirection.
  359. elementType = ((PointerType)elementType).ElementType;
  360. }
  361. ILVariable newVar = new ILVariable(
  362. VariableKind.PinnedLocal,
  363. new PointerType(elementType),
  364. oldVar.Index);
  365. newVar.Name = oldVar.Name;
  366. newVar.HasGeneratedName = oldVar.HasGeneratedName;
  367. oldVar.Function.Variables.Add(newVar);
  368. ReplacePinnedVar(oldVar, newVar, pinnedRegion);
  369. UseExistingVariableForPinnedRegion(pinnedRegion);
  370. } else if (pinnedRegion.Variable.Type.Kind == TypeKind.Array) {
  371. context.Step("Replace pinned array with native pointer", pinnedRegion);
  372. MoveArrayToPointerToPinnedRegionInit(pinnedRegion);
  373. UseExistingVariableForPinnedRegion(pinnedRegion);
  374. } else if (pinnedRegion.Variable.Type.IsKnownType(KnownTypeCode.String)) {
  375. // fixing a string
  376. ILVariable nativeVar;
  377. ILInstruction initInst;
  378. // stloc nativeVar(conv o->i (ldloc pinnedVar))
  379. // if (comp(ldloc nativeVar == conv i4->i <sign extend>(ldc.i4 0))) br targetBlock
  380. // br adjustOffsetToStringData
  381. Block targetBlock, adjustOffsetToStringData;
  382. if (body.EntryPoint.IncomingEdgeCount == 1
  383. && body.EntryPoint.Instructions.Count == 3
  384. && body.EntryPoint.Instructions[0].MatchStLoc(out nativeVar, out initInst)
  385. && nativeVar.Type.GetStackType() == StackType.I
  386. && nativeVar.StoreCount == 2
  387. && initInst.UnwrapConv(ConversionKind.StopGCTracking).MatchLdLoc(pinnedRegion.Variable)
  388. && IsBranchOnNull(body.EntryPoint.Instructions[1], nativeVar, out targetBlock)
  389. && targetBlock.Parent == body
  390. && body.EntryPoint.Instructions[2].MatchBranch(out adjustOffsetToStringData)
  391. && adjustOffsetToStringData.Parent == body && adjustOffsetToStringData.IncomingEdgeCount == 1
  392. && IsOffsetToStringDataBlock(adjustOffsetToStringData, nativeVar, targetBlock))
  393. {
  394. context.Step("Handle pinned string (with adjustOffsetToStringData)", pinnedRegion);
  395. // remove old entry point
  396. body.Blocks.RemoveAt(0);
  397. body.Blocks.RemoveAt(adjustOffsetToStringData.ChildIndex);
  398. // make targetBlock the new entry point
  399. body.Blocks.RemoveAt(targetBlock.ChildIndex);
  400. body.Blocks.Insert(0, targetBlock);
  401. pinnedRegion.Init = new ArrayToPointer(pinnedRegion.Init);
  402. ILVariable otherVar;
  403. ILInstruction otherVarInit;
  404. // In optimized builds, the 'nativeVar' may end up being a stack slot,
  405. // and only gets assigned to a real variable after the offset adjustment.
  406. if (nativeVar.Kind == VariableKind.StackSlot && nativeVar.LoadCount == 1
  407. && body.EntryPoint.Instructions[0].MatchStLoc(out otherVar, out otherVarInit)
  408. && otherVarInit.MatchLdLoc(nativeVar)
  409. && otherVar.IsSingleDefinition)
  410. {
  411. body.EntryPoint.Instructions.RemoveAt(0);
  412. nativeVar = otherVar;
  413. }
  414. ILVariable newVar;
  415. if (nativeVar.Kind == VariableKind.Local) {
  416. newVar = new ILVariable(VariableKind.PinnedLocal, nativeVar.Type, nativeVar.Index);
  417. newVar.Name = nativeVar.Name;
  418. newVar.HasGeneratedName = nativeVar.HasGeneratedName;
  419. nativeVar.Function.Variables.Add(newVar);
  420. ReplacePinnedVar(nativeVar, newVar, pinnedRegion);
  421. } else {
  422. newVar = nativeVar;
  423. }
  424. ReplacePinnedVar(pinnedRegion.Variable, newVar, pinnedRegion);
  425. }
  426. }
  427. // Detect nested pinned regions:
  428. foreach (var block in body.Blocks)
  429. CreatePinnedRegion(block);
  430. body.Blocks.RemoveAll(b => b.Instructions.Count == 0); // remove dummy blocks
  431. }
  432. private void MoveArrayToPointerToPinnedRegionInit(PinnedRegion pinnedRegion)
  433. {
  434. // Roslyn started marking the array variable as pinned,
  435. // and then uses array.to.pointer immediately within the region.
  436. Debug.Assert(pinnedRegion.Variable.Type.Kind == TypeKind.Array);
  437. if (pinnedRegion.Variable.StoreInstructions.Count != 1 || pinnedRegion.Variable.AddressCount != 0 || pinnedRegion.Variable.LoadCount != 1)
  438. return;
  439. var ldloc = pinnedRegion.Variable.LoadInstructions.Single();
  440. if (!(ldloc.Parent is ArrayToPointer arrayToPointer))
  441. return;
  442. if (!(arrayToPointer.Parent is Conv conv && conv.Kind == ConversionKind.StopGCTracking))
  443. return;
  444. Debug.Assert(arrayToPointer.IsDescendantOf(pinnedRegion));
  445. ILVariable oldVar = pinnedRegion.Variable;
  446. ILVariable newVar = new ILVariable(
  447. VariableKind.PinnedLocal,
  448. new PointerType(((ArrayType)oldVar.Type).ElementType),
  449. oldVar.Index);
  450. newVar.Name = oldVar.Name;
  451. newVar.HasGeneratedName = oldVar.HasGeneratedName;
  452. oldVar.Function.Variables.Add(newVar);
  453. pinnedRegion.Variable = newVar;
  454. pinnedRegion.Init = new ArrayToPointer(pinnedRegion.Init) { ILRange = arrayToPointer.ILRange };
  455. conv.ReplaceWith(new LdLoc(newVar) { ILRange = conv.ILRange });
  456. }
  457. void ReplacePinnedVar(ILVariable oldVar, ILVariable newVar, ILInstruction inst)
  458. {
  459. Debug.Assert(newVar.StackType == StackType.I);
  460. if (inst is Conv conv && conv.Kind == ConversionKind.StopGCTracking && conv.Argument.MatchLdLoc(oldVar) && conv.ResultType == newVar.StackType) {
  461. // conv ref->i (ldloc oldVar)
  462. // => ldloc newVar
  463. conv.AddILRange(conv.Argument.ILRange);
  464. conv.ReplaceWith(new LdLoc(newVar) { ILRange = conv.ILRange });
  465. return;
  466. }
  467. if (inst is IInstructionWithVariableOperand iwvo && iwvo.Variable == oldVar) {
  468. iwvo.Variable = newVar;
  469. if (inst is StLoc stloc && oldVar.Type.Kind == TypeKind.ByReference) {
  470. stloc.Value = new Conv(stloc.Value, PrimitiveType.I, false, Sign.None);
  471. }
  472. if ((inst is LdLoc || inst is StLoc) && !IsSlotAcceptingBothManagedAndUnmanagedPointers(inst.SlotInfo) && oldVar.StackType != StackType.I) {
  473. // wrap inst in Conv, so that the stack types match up
  474. var children = inst.Parent.Children;
  475. children[inst.ChildIndex] = new Conv(inst, PrimitiveType.I, false, Sign.None);
  476. }
  477. } else if (inst.MatchLdStr(out var val) && val == "Is this ILSpy?") {
  478. inst.ReplaceWith(new LdStr("This is ILSpy!")); // easter egg ;)
  479. return;
  480. }
  481. foreach (var child in inst.Children) {
  482. ReplacePinnedVar(oldVar, newVar, child);
  483. }
  484. }
  485. private bool IsSlotAcceptingBothManagedAndUnmanagedPointers(SlotInfo slotInfo)
  486. {
  487. return slotInfo == Block.InstructionSlot || slotInfo == LdObj.TargetSlot || slotInfo == StObj.TargetSlot;
  488. }
  489. bool IsBranchOnNull(ILInstruction condBranch, ILVariable nativeVar, out Block targetBlock)
  490. {
  491. targetBlock = null;
  492. // if (comp(ldloc nativeVar == conv i4->i <sign extend>(ldc.i4 0))) br targetBlock
  493. ILInstruction condition, trueInst, left, right;
  494. return condBranch.MatchIfInstruction(out condition, out trueInst)
  495. && condition.MatchCompEquals(out left, out right)
  496. && left.MatchLdLoc(nativeVar) && IsNullOrZero(right)
  497. && trueInst.MatchBranch(out targetBlock);
  498. }
  499. bool IsOffsetToStringDataBlock(Block block, ILVariable nativeVar, Block targetBlock)
  500. {
  501. // stloc nativeVar(add(ldloc nativeVar, conv i4->i <sign extend>(call [Accessor System.Runtime.CompilerServices.RuntimeHelpers.get_OffsetToStringData():System.Int32]())))
  502. // br IL_0011
  503. ILInstruction left, right, value;
  504. return block.Instructions.Count == 2
  505. && block.Instructions[0].MatchStLoc(nativeVar, out value)
  506. && value.MatchBinaryNumericInstruction(BinaryNumericOperator.Add, out left, out right)
  507. && left.MatchLdLoc(nativeVar)
  508. && IsOffsetToStringDataCall(right)
  509. && block.Instructions[1].MatchBranch(targetBlock);
  510. }
  511. bool IsOffsetToStringDataCall(ILInstruction inst)
  512. {
  513. Call call = inst.UnwrapConv(ConversionKind.SignExtend) as Call;
  514. return call != null && call.Method.FullName == "System.Runtime.CompilerServices.RuntimeHelpers.get_OffsetToStringData";
  515. }
  516. /// <summary>
  517. /// Modifies a pinned region to eliminate an extra local variable that roslyn tends to generate.
  518. /// </summary>
  519. void UseExistingVariableForPinnedRegion(PinnedRegion pinnedRegion)
  520. {
  521. // PinnedRegion V_1(..., BlockContainer {
  522. // Block IL_0000(incoming: 1) {
  523. // stloc V_0(ldloc V_1)
  524. // ...
  525. if (!(pinnedRegion.Body is BlockContainer body))
  526. return;
  527. if (pinnedRegion.Variable.LoadCount != 1)
  528. return;
  529. if (!body.EntryPoint.Instructions[0].MatchStLoc(out var v, out var init))
  530. return;
  531. if (!init.MatchLdLoc(pinnedRegion.Variable))
  532. return;
  533. if (!(v.IsSingleDefinition && v.Type.Equals(pinnedRegion.Variable.Type)))
  534. return;
  535. if (v.Kind != VariableKind.Local)
  536. return;
  537. // replace V_1 with V_0
  538. v.Kind = VariableKind.PinnedLocal;
  539. pinnedRegion.Variable = v;
  540. body.EntryPoint.Instructions.RemoveAt(0);
  541. }
  542. #endregion
  543. }
  544. }