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.

680 lines
31 KiB

  1. // Copyright (c) 2014 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.FlowAnalysis;
  23. using ICSharpCode.Decompiler.IL.Transforms;
  24. using ICSharpCode.Decompiler.Util;
  25. namespace ICSharpCode.Decompiler.IL.ControlFlow
  26. {
  27. /// <summary>
  28. /// Detect loops in IL AST.
  29. /// </summary>
  30. /// <remarks>
  31. /// Transform ordering:
  32. /// * LoopDetection should run before other control flow structures are detected.
  33. /// * Blocks should be basic blocks (not extended basic blocks) so that the natural loops
  34. /// don't include more instructions than strictly necessary.
  35. /// * Loop detection should run after the 'return block' is duplicated (ControlFlowSimplification).
  36. /// </remarks>
  37. public class LoopDetection : IBlockTransform
  38. {
  39. BlockTransformContext context;
  40. /// <summary>Block container corresponding to the current cfg.</summary>
  41. BlockContainer currentBlockContainer;
  42. /// <summary>
  43. /// Check whether 'block' is a loop head; and construct a loop instruction
  44. /// (nested BlockContainer) if it is.
  45. /// </summary>
  46. public void Run(Block block, BlockTransformContext context)
  47. {
  48. this.context = context;
  49. // LoopDetection runs early enough so that block should still
  50. // be in the original container at this point.
  51. Debug.Assert(block.Parent == context.ControlFlowGraph.Container);
  52. this.currentBlockContainer = context.ControlFlowGraph.Container;
  53. // Because this is a post-order block transform, we can assume that
  54. // any nested loops within this loop have already been constructed.
  55. if (block.Instructions.Last() is SwitchInstruction switchInst) {
  56. // Switch instructions support "break;" just like loops
  57. DetectSwitchBody(block, switchInst);
  58. }
  59. ControlFlowNode h = context.ControlFlowNode; // CFG node for our potential loop head
  60. Debug.Assert(h.UserData == block);
  61. Debug.Assert(!TreeTraversal.PreOrder(h, n => n.DominatorTreeChildren).Any(n => n.Visited));
  62. List<ControlFlowNode> loop = null;
  63. foreach (var t in h.Predecessors) {
  64. if (h.Dominates(t)) {
  65. // h->t is a back edge, and h is a loop header
  66. // Add the natural loop of t->h to the loop.
  67. // Definitions:
  68. // * A back edge is an edge t->h so that h dominates t.
  69. // * The natural loop of the back edge is the smallest set of nodes
  70. // that includes the back edge and has no predecessors outside the set
  71. // except for the predecessor of the header.
  72. if (loop == null) {
  73. loop = new List<ControlFlowNode>();
  74. loop.Add(h);
  75. // Mark loop header as visited so that the pre-order traversal
  76. // stops at the loop header.
  77. h.Visited = true;
  78. }
  79. t.TraversePreOrder(n => n.Predecessors, loop.Add);
  80. }
  81. }
  82. if (loop != null) {
  83. var headBlock = (Block)h.UserData;
  84. context.Step($"Construct loop with head {headBlock.Label}", headBlock);
  85. // loop now is the union of all natural loops with loop head h.
  86. // Ensure any block included into nested loops is also considered part of this loop:
  87. IncludeNestedContainers(loop);
  88. // Try to extend the loop to reduce the number of exit points:
  89. ExtendLoop(h, loop, out var exitPoint);
  90. // Sort blocks in the loop in reverse post-order to make the output look a bit nicer.
  91. // (if the loop doesn't contain nested loops, this is a topological sort)
  92. loop.Sort((a, b) => b.PostOrderNumber.CompareTo(a.PostOrderNumber));
  93. Debug.Assert(loop[0] == h);
  94. foreach (var node in loop) {
  95. node.Visited = false; // reset visited flag so that we can find outer loops
  96. Debug.Assert(h.Dominates(node) || !node.IsReachable, "The loop body must be dominated by the loop head");
  97. }
  98. ConstructLoop(loop, exitPoint);
  99. }
  100. }
  101. /// <summary>
  102. /// For each block in the input loop that is the head of a nested loop or switch,
  103. /// include all blocks from the nested container into the loop.
  104. ///
  105. /// This ensures that all blocks that were included into inner loops are also
  106. /// included into the outer loop, thus keeping our loops well-nested.
  107. /// </summary>
  108. /// <remarks>
  109. /// More details for why this is necessary are here:
  110. /// https://github.com/icsharpcode/ILSpy/issues/915
  111. ///
  112. /// Pre+Post-Condition: node.Visited iff loop.Contains(node)
  113. /// </remarks>
  114. void IncludeNestedContainers(List<ControlFlowNode> loop)
  115. {
  116. for (int i = 0; i < loop.Count; i++) {
  117. IncludeBlock((Block)loop[i].UserData);
  118. }
  119. void IncludeBlock(Block block)
  120. {
  121. if (block.Instructions[0] is BlockContainer nestedContainer) {
  122. // Just in case the block has multiple nested containers (e.g. due to loop and switch),
  123. // also check the entry point:
  124. IncludeBlock(nestedContainer.EntryPoint);
  125. // Use normal processing for all non-entry-point blocks
  126. // (the entry-point itself doesn't have a CFG node, because it's newly created by this transform)
  127. for (int i = 1; i < nestedContainer.Blocks.Count; i++) {
  128. var node = context.ControlFlowGraph.GetNode(nestedContainer.Blocks[i]);
  129. Debug.Assert(loop[0].Dominates(node));
  130. if (!node.Visited) {
  131. node.Visited = true;
  132. loop.Add(node);
  133. // note: this block will be re-visited when the "i < loop.Count"
  134. // gets around to the new entry
  135. }
  136. }
  137. }
  138. }
  139. }
  140. #region ExtendLoop
  141. /// <summary>
  142. /// Given a natural loop, add additional CFG nodes to the loop in order
  143. /// to reduce the number of exit points out of the loop.
  144. /// We do this because C# only allows reaching a single exit point (with 'break'
  145. /// statements or when the loop condition evaluates to false), so we'd have
  146. /// to introduce 'goto' statements for any additional exit points.
  147. /// </summary>
  148. /// <remarks>
  149. /// Definition:
  150. /// A "reachable exit" is a branch/leave target that is reachable from the loop,
  151. /// but not dominated by the loop head. A reachable exit may or may not have a
  152. /// corresponding CFG node (depending on whether it is a block in the current block container).
  153. /// -> reachable exits are leaving the code region dominated by the loop
  154. ///
  155. /// Definition:
  156. /// A loop "exit point" is a CFG node that is not itself part of the loop,
  157. /// but has at least one predecessor which is part of the loop.
  158. /// -> exit points are leaving the loop itself
  159. ///
  160. /// Nodes can only be added to the loop if they are dominated by the loop head.
  161. /// When adding a node to the loop, we must also add all of that node's predecessors
  162. /// to the loop. (this ensures that the loop keeps its single entry point)
  163. ///
  164. /// Goal: If possible, find a set of nodes that can be added to the loop so that there
  165. /// remains only a single exit point.
  166. /// Add as little code as possible to the loop to reach this goal.
  167. ///
  168. /// This means we need to partition the set of nodes dominated by the loop entry point
  169. /// into two sets (in-loop and out-of-loop).
  170. /// Constraints:
  171. /// * the loop head itself is in-loop
  172. /// * there must not be any edge from an out-of-loop node to an in-loop node
  173. /// -> all predecessors of in-loop nodes are also in-loop
  174. /// -> all nodes in a cycle are part of the same partition
  175. /// Optimize:
  176. /// * use only a single exit point if at all possible
  177. /// * minimize the amount of code in the in-loop partition
  178. /// (thus: maximize the amount of code in the out-of-loop partition)
  179. /// "amount of code" could be measured as:
  180. /// * number of basic blocks
  181. /// * number of instructions directly in those basic blocks (~= number of statements)
  182. /// * number of instructions in those basic blocks (~= number of expressions)
  183. /// (we currently use the number of statements)
  184. ///
  185. /// Observations:
  186. /// * If a node is in-loop, so are all its ancestors in the dominator tree (up to the loop entry point)
  187. /// * If there are no exits reachable from a node (i.e. all paths from that node lead to a return/throw instruction),
  188. /// it is valid to put the group of nodes dominated by that node into either partition independently of
  189. /// any other nodes except for the ancestors in the dominator tree.
  190. /// (exception: the loop head itself must always be in-loop)
  191. ///
  192. /// There are two different cases we need to consider:
  193. /// 1) There are no exits reachable at all from the loop head.
  194. /// -> it is possible to create a loop with zero exit points by adding all nodes
  195. /// dominated by the loop to the loop.
  196. /// -> the only way to exit the loop is by "return;" or "throw;"
  197. /// 2) There are some exits reachable from the loop head.
  198. ///
  199. /// In case 1, we can pick a single exit point freely by picking any node that has no reachable exits
  200. /// (other than the loop head).
  201. /// All nodes dominated by the exit point are out-of-loop, all other nodes are in-loop.
  202. /// See PickExitPoint() for the heuristic that picks the exit point in this case.
  203. ///
  204. /// In case 2, we need to pick our exit point so that all paths from the loop head
  205. /// to the reachable exits run through that exit point.
  206. ///
  207. /// This is a form of postdominance where the reachable exits are considered exit nodes,
  208. /// while "return;" or "throw;" instructions are not considered exit nodes.
  209. ///
  210. /// Using this form of postdominance, we are looking for an exit point that post-dominates all nodes in the natural loop.
  211. /// --> a common ancestor in post-dominator tree.
  212. /// To minimize the amount of code in-loop, we pick the lowest common ancestor.
  213. /// All nodes dominated by the exit point are out-of-loop, all other nodes are in-loop.
  214. /// (using normal dominance as in case 1, not post-dominance!)
  215. ///
  216. /// If it is impossible to use a single exit point for the loop, the lowest common ancestor will be the fake "exit node"
  217. /// used by the post-dominance analysis. In this case, we fall back to the old heuristic algorithm.
  218. ///
  219. /// Precondition: Requires that a node is marked as visited iff it is contained in the loop.
  220. /// </remarks>
  221. void ExtendLoop(ControlFlowNode loopHead, List<ControlFlowNode> loop, out ControlFlowNode exitPoint, bool isSwitch=false)
  222. {
  223. exitPoint = FindExitPoint(loopHead, loop, isSwitch);
  224. Debug.Assert(!loop.Contains(exitPoint), "Cannot pick an exit point that is part of the natural loop");
  225. if (exitPoint != null) {
  226. // Either we are in case 1 and just picked an exit that maximizes the amount of code
  227. // outside the loop, or we are in case 2 and found an exit point via post-dominance.
  228. // Note that if exitPoint == NoExitPoint, we end up adding all dominated blocks to the loop.
  229. var ep = exitPoint;
  230. foreach (var node in TreeTraversal.PreOrder(loopHead, n => (n != ep) ? n.DominatorTreeChildren : null)) {
  231. if (node != exitPoint && !node.Visited) {
  232. loop.Add(node);
  233. }
  234. }
  235. } else {
  236. // We are in case 2, but could not find a suitable exit point.
  237. // Heuristically try to minimize the number of exit points
  238. // (but we'll always end up with more than 1 exit and will require goto statements).
  239. ExtendLoopHeuristic(loopHead, loop, loopHead);
  240. }
  241. }
  242. /// <summary>
  243. /// Special control flow node (not part of any graph) that signifies that we want to construct a loop
  244. /// without any exit point.
  245. /// </summary>
  246. static readonly ControlFlowNode NoExitPoint = new ControlFlowNode();
  247. /// <summary>
  248. /// Finds a suitable single exit point for the specified loop.
  249. /// </summary>
  250. /// <returns>
  251. /// 1) If a suitable exit point was found: the control flow block that should be reached when breaking from the loop
  252. /// 2) If the loop should not have any exit point (extend by all dominated blocks): NoExitPoint
  253. /// 3) otherwise (exit point unknown, heuristically extend loop): null
  254. /// </returns>
  255. /// <remarks>This method must not write to the Visited flags on the CFG.</remarks>
  256. ControlFlowNode FindExitPoint(ControlFlowNode loopHead, IReadOnlyList<ControlFlowNode> naturalLoop, bool treatBackEdgesAsExits)
  257. {
  258. bool hasReachableExit = context.ControlFlowGraph.HasReachableExit(loopHead);
  259. if (!hasReachableExit && treatBackEdgesAsExits) {
  260. // If we're analyzing the switch, there's no reachable exit, but the loopHead (=switchHead) block
  261. // is also a loop head, we consider the back-edge a reachable exit for the switch.
  262. hasReachableExit = loopHead.Predecessors.Any(p => loopHead.Dominates(p));
  263. }
  264. if (!hasReachableExit) {
  265. // Case 1:
  266. // There are no nodes n so that loopHead dominates a predecessor of n but not n itself
  267. // -> we could build a loop with zero exit points.
  268. if (IsPossibleForeachLoop((Block)loopHead.UserData, out var exitBranch)) {
  269. if (exitBranch != null) {
  270. // let's see if the target of the exit branch is a suitable exit point
  271. var cfgNode = loopHead.Successors.FirstOrDefault(n => n.UserData == exitBranch.TargetBlock);
  272. if (cfgNode != null && loopHead.Dominates(cfgNode) && !context.ControlFlowGraph.HasReachableExit(cfgNode)) {
  273. return cfgNode;
  274. }
  275. }
  276. return NoExitPoint;
  277. }
  278. ControlFlowNode exitPoint = null;
  279. int exitPointILOffset = -1;
  280. foreach (var node in loopHead.DominatorTreeChildren) {
  281. PickExitPoint(node, ref exitPoint, ref exitPointILOffset);
  282. }
  283. return exitPoint;
  284. } else {
  285. // Case 2:
  286. // We need to pick our exit point so that all paths from the loop head
  287. // to the reachable exits run through that exit point.
  288. var cfg = context.ControlFlowGraph.cfg;
  289. var revCfg = PrepareReverseCFG(loopHead, treatBackEdgesAsExits, out int exitNodeArity);
  290. //ControlFlowNode.ExportGraph(cfg).Show("cfg");
  291. //ControlFlowNode.ExportGraph(revCfg).Show("rev");
  292. ControlFlowNode commonAncestor = revCfg[loopHead.UserIndex];
  293. Debug.Assert(commonAncestor.IsReachable);
  294. foreach (ControlFlowNode cfgNode in naturalLoop) {
  295. ControlFlowNode revNode = revCfg[cfgNode.UserIndex];
  296. if (revNode.IsReachable) {
  297. commonAncestor = Dominance.FindCommonDominator(commonAncestor, revNode);
  298. }
  299. }
  300. // All paths from within the loop to a reachable exit run through 'commonAncestor'.
  301. // However, this doesn't mean that 'commonAncestor' is valid as an exit point.
  302. // We walk up the post-dominator tree until we've got a valid exit point:
  303. ControlFlowNode exitPoint;
  304. while (commonAncestor.UserIndex >= 0) {
  305. exitPoint = cfg[commonAncestor.UserIndex];
  306. Debug.Assert(exitPoint.Visited == naturalLoop.Contains(exitPoint));
  307. // It's possible that 'commonAncestor' is itself part of the natural loop.
  308. // If so, it's not a valid exit point.
  309. if (!exitPoint.Visited && ValidateExitPoint(loopHead, exitPoint)) {
  310. // we found an exit point
  311. return exitPoint;
  312. }
  313. commonAncestor = commonAncestor.ImmediateDominator;
  314. }
  315. // least common post-dominator is the artificial exit node
  316. // This means we're in one of two cases:
  317. // * The loop might have multiple exit points.
  318. // -> we should return null
  319. // * The loop has a single exit point that wasn't considered during post-dominance analysis.
  320. // (which means the single exit isn't dominated by the loop head)
  321. // -> we should return NoExitPoint so that all code dominated by the loop head is included into the loop
  322. if (exitNodeArity > 1) {
  323. return null;
  324. } else {
  325. // If exitNodeArity == 0, we should maybe look test if our exits out of the block container are all compatible?
  326. // but I don't think it hurts to have a bit too much code inside the loop in this rare case.
  327. return NoExitPoint;
  328. }
  329. }
  330. }
  331. /// <summary>
  332. /// Validates an exit point.
  333. ///
  334. /// An exit point is invalid iff there is a node reachable from the exit point that
  335. /// is dominated by the loop head, but not by the exit point.
  336. /// (i.e. this method returns false iff the exit point's dominance frontier contains
  337. /// a node dominated by the loop head. but we implement this the slow way because
  338. /// we don't have dominance frontiers precomputed)
  339. /// </summary>
  340. /// <remarks>
  341. /// We need this because it's possible that there's a return block (thus reverse-unreachable node ignored by post-dominance)
  342. /// that is reachable both directly from the loop, and from the exit point.
  343. /// </remarks>
  344. bool ValidateExitPoint(ControlFlowNode loopHead, ControlFlowNode exitPoint)
  345. {
  346. var cfg = context.ControlFlowGraph;
  347. return IsValid(exitPoint);
  348. bool IsValid(ControlFlowNode node)
  349. {
  350. if (!cfg.HasReachableExit(node)) {
  351. // Optimization: if the dominance frontier is empty, we don't need
  352. // to check every node.
  353. return true;
  354. }
  355. foreach (var succ in node.Successors) {
  356. if (loopHead != succ && loopHead.Dominates(succ) && !exitPoint.Dominates(succ))
  357. return false;
  358. }
  359. foreach (var child in node.DominatorTreeChildren) {
  360. if (!IsValid(child))
  361. return false;
  362. }
  363. return true;
  364. }
  365. }
  366. /// <summary>
  367. /// Pick exit point by picking any node that has no reachable exits.
  368. ///
  369. /// In the common case where the code was compiled with a compiler that emits IL code
  370. /// in source order (like the C# compiler), we can find the "real" exit point
  371. /// by simply picking the block with the highest IL offset.
  372. /// So let's do that instead of maximizing amount of code.
  373. /// </summary>
  374. /// <returns>Code amount in <paramref name="node"/> and its dominated nodes.</returns>
  375. /// <remarks>This method must not write to the Visited flags on the CFG.</remarks>
  376. void PickExitPoint(ControlFlowNode node, ref ControlFlowNode exitPoint, ref int exitPointILOffset)
  377. {
  378. Block block = (Block)node.UserData;
  379. if (block.ILRange.Start > exitPointILOffset
  380. && !context.ControlFlowGraph.HasReachableExit(node)
  381. && ((Block)node.UserData).Parent == currentBlockContainer)
  382. {
  383. // HasReachableExit(node) == false
  384. // -> there are no nodes n so that `node` dominates a predecessor of n but not n itself
  385. // -> there is no control flow out of `node` back into the loop, so it's usable as exit point
  386. // Additionally, we require that the block wasn't already moved into a nested loop,
  387. // since there's no way to jump into the middle of that loop when we need to exit.
  388. // NB: this is the only reason why we detect nested loops before outer loops:
  389. // If we detected the outer loop first, the outer loop might pick an exit point
  390. // that prevents us from finding a nice exit for the inner loops, causing
  391. // unnecessary gotos.
  392. exitPoint = node;
  393. exitPointILOffset = block.ILRange.Start;
  394. return; // don't visit children, they are likely to have even later IL offsets and we'd end up
  395. // moving almost all of the code into the loop.
  396. }
  397. foreach (var child in node.DominatorTreeChildren) {
  398. PickExitPoint(child, ref exitPoint, ref exitPointILOffset);
  399. }
  400. }
  401. /// <summary>
  402. /// Constructs a new control flow graph.
  403. /// Each node cfg[i] has a corresponding node rev[i].
  404. /// Edges are only created for nodes dominated by loopHead, and are in reverse from their direction
  405. /// in the primary CFG.
  406. /// An artificial exit node is used for edges that leave the set of nodes dominated by loopHead,
  407. /// or that leave the block Container.
  408. /// </summary>
  409. /// <param name="loopHead">Entry point of the loop.</param>
  410. /// <param name="treatBackEdgesAsExits">Whether to treat loop back edges as exit points.</param>
  411. /// <param name="exitNodeArity">out: The number of different CFG nodes.
  412. /// Possible values:
  413. /// 0 = no CFG nodes used as exit nodes (although edges leaving the block container might still be exits);
  414. /// 1 = a single CFG node (not dominated by loopHead) was used as an exit node;
  415. /// 2 = more than one CFG node (not dominated by loopHead) was used as an exit node.
  416. /// </param>
  417. /// <returns></returns>
  418. ControlFlowNode[] PrepareReverseCFG(ControlFlowNode loopHead, bool treatBackEdgesAsExits, out int exitNodeArity)
  419. {
  420. ControlFlowNode[] cfg = context.ControlFlowGraph.cfg;
  421. ControlFlowNode[] rev = new ControlFlowNode[cfg.Length + 1];
  422. for (int i = 0; i < cfg.Length; i++) {
  423. rev[i] = new ControlFlowNode { UserIndex = i, UserData = cfg[i].UserData };
  424. }
  425. ControlFlowNode nodeTreatedAsExitNode = null;
  426. bool multipleNodesTreatedAsExitNodes = false;
  427. ControlFlowNode exitNode = new ControlFlowNode { UserIndex = -1 };
  428. rev[cfg.Length] = exitNode;
  429. for (int i = 0; i < cfg.Length; i++) {
  430. if (!loopHead.Dominates(cfg[i]))
  431. continue;
  432. // Add reverse edges for all edges in cfg
  433. foreach (var succ in cfg[i].Successors) {
  434. if (loopHead.Dominates(succ) && (!treatBackEdgesAsExits || loopHead != succ)) {
  435. rev[succ.UserIndex].AddEdgeTo(rev[i]);
  436. } else {
  437. if (nodeTreatedAsExitNode == null)
  438. nodeTreatedAsExitNode = succ;
  439. if (nodeTreatedAsExitNode != succ)
  440. multipleNodesTreatedAsExitNodes = true;
  441. exitNode.AddEdgeTo(rev[i]);
  442. }
  443. }
  444. if (context.ControlFlowGraph.HasDirectExitOutOfContainer(cfg[i])) {
  445. exitNode.AddEdgeTo(rev[i]);
  446. }
  447. }
  448. if (multipleNodesTreatedAsExitNodes)
  449. exitNodeArity = 2; // more than 1
  450. else if (nodeTreatedAsExitNode != null)
  451. exitNodeArity = 1;
  452. else
  453. exitNodeArity = 0;
  454. Dominance.ComputeDominance(exitNode, context.CancellationToken);
  455. return rev;
  456. }
  457. static bool IsPossibleForeachLoop(Block loopHead, out Branch exitBranch)
  458. {
  459. exitBranch = null;
  460. var container = (BlockContainer)loopHead.Parent;
  461. if (!(container.SlotInfo == TryInstruction.TryBlockSlot && container.Parent is TryFinally))
  462. return false;
  463. if (loopHead.Instructions.Count != 2)
  464. return false;
  465. if (!loopHead.Instructions[0].MatchIfInstruction(out var condition, out var trueInst))
  466. return false;
  467. var falseInst = loopHead.Instructions[1];
  468. while (condition.MatchLogicNot(out var arg)) {
  469. condition = arg;
  470. ExtensionMethods.Swap(ref trueInst, ref falseInst);
  471. }
  472. if (!(condition is CallInstruction call && call.Method.Name == "MoveNext"))
  473. return false;
  474. if (!(call.Arguments.Count == 1 && call.Arguments[0].MatchLdLocRef(out var enumeratorVar)))
  475. return false;
  476. exitBranch = falseInst as Branch;
  477. // Check that loopHead is entry-point of try-block:
  478. Block entryPoint = container.EntryPoint;
  479. while (entryPoint.IncomingEdgeCount == 1 && entryPoint.Instructions.Count == 1 && entryPoint.Instructions[0].MatchBranch(out var targetBlock)) {
  480. // skip blocks that only branch to another block
  481. entryPoint = targetBlock;
  482. }
  483. return entryPoint == loopHead;
  484. }
  485. #endregion
  486. #region ExtendLoop (fall-back heuristic)
  487. /// <summary>
  488. /// This function implements a heuristic algorithm that tries to reduce the number of exit
  489. /// points. It is only used as fall-back when it is impossible to use a single exit point.
  490. /// </summary>
  491. /// <remarks>
  492. /// This heuristic loop extension algorithm traverses the loop head's dominator tree in pre-order.
  493. /// For each candidate node, we detect whether adding it to the loop reduces the number of exit points.
  494. /// If it does, the candidate is added to the loop.
  495. ///
  496. /// Adding a node to the loop has two effects on the the number of exit points:
  497. /// * exit points that were added to the loop are no longer exit points, thus reducing the total number of exit points
  498. /// * successors of the newly added nodes might be new, additional exit points
  499. ///
  500. /// Requires and maintains the invariant that a node is marked as visited iff it is contained in the loop.
  501. /// </remarks>
  502. void ExtendLoopHeuristic(ControlFlowNode loopHead, List<ControlFlowNode> loop, ControlFlowNode candidate)
  503. {
  504. Debug.Assert(candidate.Visited == loop.Contains(candidate));
  505. if (!candidate.Visited) {
  506. // This node not yet part of the loop, but might be added
  507. List<ControlFlowNode> additionalNodes = new List<ControlFlowNode>();
  508. // Find additionalNodes nodes and mark them as visited.
  509. candidate.TraversePreOrder(n => n.Predecessors, additionalNodes.Add);
  510. // This means Visited now represents the candiate extended loop.
  511. // Determine new exit points that are reachable from the additional nodes
  512. // (note: some of these might have previously been exit points, too)
  513. var newExitPoints = additionalNodes.SelectMany(n => n.Successors).Where(n => !n.Visited).ToHashSet();
  514. // Make visited represent the unextended loop, so that we can measure the exit points
  515. // in the old state.
  516. foreach (var node in additionalNodes)
  517. node.Visited = false;
  518. // Measure number of added and removed exit points
  519. int removedExitPoints = additionalNodes.Count(IsExitPoint);
  520. int addedExitPoints = newExitPoints.Count(n => !IsExitPoint(n));
  521. if (removedExitPoints > addedExitPoints) {
  522. // We can reduce the number of exit points by adding the candidate node to the loop.
  523. candidate.TraversePreOrder(n => n.Predecessors, loop.Add);
  524. }
  525. }
  526. // Pre-order traversal of dominator tree
  527. foreach (var node in candidate.DominatorTreeChildren) {
  528. ExtendLoopHeuristic(loopHead, loop, node);
  529. }
  530. }
  531. /// <summary>
  532. /// Gets whether 'node' is an exit point for the loop marked by the Visited flag.
  533. /// </summary>
  534. bool IsExitPoint(ControlFlowNode node)
  535. {
  536. if (node.Visited)
  537. return false; // nodes in the loop are not exit points
  538. foreach (var pred in node.Predecessors) {
  539. if (pred.Visited)
  540. return true;
  541. }
  542. return false;
  543. }
  544. #endregion
  545. /// <summary>
  546. /// Move the blocks associated with the loop into a new block container.
  547. /// </summary>
  548. void ConstructLoop(List<ControlFlowNode> loop, ControlFlowNode exitPoint)
  549. {
  550. Block oldEntryPoint = (Block)loop[0].UserData;
  551. Block exitTargetBlock = (Block)exitPoint?.UserData;
  552. BlockContainer loopContainer = new BlockContainer(ContainerKind.Loop);
  553. Block newEntryPoint = new Block();
  554. loopContainer.Blocks.Add(newEntryPoint);
  555. // Move contents of oldEntryPoint to newEntryPoint
  556. // (we can't move the block itself because it might be the target of branch instructions outside the loop)
  557. newEntryPoint.Instructions.ReplaceList(oldEntryPoint.Instructions);
  558. newEntryPoint.ILRange = oldEntryPoint.ILRange;
  559. oldEntryPoint.Instructions.ReplaceList(new[] { loopContainer });
  560. if (exitTargetBlock != null)
  561. oldEntryPoint.Instructions.Add(new Branch(exitTargetBlock));
  562. MoveBlocksIntoContainer(loop, loopContainer);
  563. // Rewrite branches within the loop from oldEntryPoint to newEntryPoint:
  564. foreach (var branch in loopContainer.Descendants.OfType<Branch>()) {
  565. if (branch.TargetBlock == oldEntryPoint) {
  566. branch.TargetBlock = newEntryPoint;
  567. } else if (branch.TargetBlock == exitTargetBlock) {
  568. branch.ReplaceWith(new Leave(loopContainer) { ILRange = branch.ILRange });
  569. }
  570. }
  571. }
  572. private void MoveBlocksIntoContainer(List<ControlFlowNode> loop, BlockContainer loopContainer)
  573. {
  574. // Move other blocks into the loop body: they're all dominated by the loop header,
  575. // and thus cannot be the target of branch instructions outside the loop.
  576. for (int i = 1; i < loop.Count; i++) {
  577. Block block = (Block)loop[i].UserData;
  578. // some blocks might already be in use by nested loops that were detected earlier;
  579. // don't move those (they'll be implicitly moved when the block containing the
  580. // nested loop container is moved).
  581. if (block.Parent == currentBlockContainer) {
  582. Debug.Assert(block.ChildIndex != 0);
  583. int oldChildIndex = block.ChildIndex;
  584. loopContainer.Blocks.Add(block);
  585. currentBlockContainer.Blocks.SwapRemoveAt(oldChildIndex);
  586. }
  587. }
  588. for (int i = 1; i < loop.Count; i++) {
  589. // Verify that we moved all loop blocks into the loop container.
  590. // If we wanted to move any blocks already in use by a nested loop,
  591. // this means we check that the whole nested loop got moved.
  592. Block block = (Block)loop[i].UserData;
  593. Debug.Assert(block.IsDescendantOf(loopContainer));
  594. }
  595. }
  596. private void DetectSwitchBody(Block block, SwitchInstruction switchInst)
  597. {
  598. Debug.Assert(block.Instructions.Last() == switchInst);
  599. ControlFlowNode h = context.ControlFlowNode; // CFG node for our switch head
  600. Debug.Assert(h.UserData == block);
  601. Debug.Assert(!TreeTraversal.PreOrder(h, n => n.DominatorTreeChildren).Any(n => n.Visited));
  602. var nodesInSwitch = new List<ControlFlowNode>();
  603. nodesInSwitch.Add(h);
  604. h.Visited = true;
  605. ExtendLoop(h, nodesInSwitch, out var exitPoint, isSwitch: true);
  606. if (exitPoint != null && exitPoint.Predecessors.Count == 1 && !context.ControlFlowGraph.HasReachableExit(exitPoint)) {
  607. // If the exit point is reachable from just one single "break;",
  608. // it's better to move the code into the switch.
  609. // (unlike loops which should not be nested unless necessary,
  610. // nesting switches makes it clearer in which cases a piece of code is reachable)
  611. nodesInSwitch.AddRange(TreeTraversal.PreOrder(exitPoint, p => p.DominatorTreeChildren));
  612. exitPoint = null;
  613. }
  614. context.Step("Create BlockContainer for switch", switchInst);
  615. // Sort blocks in the loop in reverse post-order to make the output look a bit nicer.
  616. // (if the loop doesn't contain nested loops, this is a topological sort)
  617. nodesInSwitch.Sort((a, b) => b.PostOrderNumber.CompareTo(a.PostOrderNumber));
  618. Debug.Assert(nodesInSwitch[0] == h);
  619. foreach (var node in nodesInSwitch) {
  620. node.Visited = false; // reset visited flag so that we can find outer loops
  621. Debug.Assert(h.Dominates(node) || !node.IsReachable, "The switch body must be dominated by the switch head");
  622. }
  623. BlockContainer switchContainer = new BlockContainer(ContainerKind.Switch);
  624. Block newEntryPoint = new Block();
  625. newEntryPoint.ILRange = switchInst.ILRange;
  626. switchContainer.Blocks.Add(newEntryPoint);
  627. newEntryPoint.Instructions.Add(switchInst);
  628. block.Instructions[block.Instructions.Count - 1] = switchContainer;
  629. Block exitTargetBlock = (Block)exitPoint?.UserData;
  630. if (exitTargetBlock != null) {
  631. block.Instructions.Add(new Branch(exitTargetBlock));
  632. }
  633. MoveBlocksIntoContainer(nodesInSwitch, switchContainer);
  634. // Rewrite branches within the loop from oldEntryPoint to newEntryPoint:
  635. foreach (var branch in switchContainer.Descendants.OfType<Branch>()) {
  636. if (branch.TargetBlock == exitTargetBlock) {
  637. branch.ReplaceWith(new Leave(switchContainer) { ILRange = branch.ILRange });
  638. }
  639. }
  640. }
  641. }
  642. }