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.

662 lines
27 KiB

  1. // Copyright (c) 2018 Siegfried Pammer
  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.Diagnostics;
  20. using System.Linq;
  21. using ICSharpCode.Decompiler.IL.ControlFlow;
  22. using ICSharpCode.Decompiler.IL.Transforms;
  23. using ICSharpCode.Decompiler.Util;
  24. namespace ICSharpCode.Decompiler.IL
  25. {
  26. /// <summary>
  27. /// Improves code quality by duplicating keyword exits to reduce nesting and restoring IL order.
  28. /// </summary>
  29. /// <remarks>
  30. /// ConditionDetection and DetectSwitchBody both have aggressive inlining policies for else blocks and default cases respectively.
  31. /// This can lead to excessive indentation when the entire rest of the method/loop is included in the else block/default case.
  32. /// When an If/SwitchInstruction is followed immediately by a keyword exit, the exit can be moved into the child blocks
  33. /// allowing the else block or default case to be moved after the if/switch as all prior cases exit.
  34. /// Most importantly, this transformation does not change the IL order of any code.
  35. ///
  36. /// ConditionDetection also has a block exit priority system to assist exit point reduction which in some cases ignores IL order.
  37. /// After HighLevelLoopTransform has run, all structures have been detected and preference can be returned to maintaining IL ordering.
  38. /// </remarks>
  39. public class ReduceNestingTransform : IILTransform
  40. {
  41. private ILTransformContext context;
  42. public void Run(ILFunction function, ILTransformContext context)
  43. {
  44. this.context = context;
  45. Visit((BlockContainer)function.Body, null);
  46. foreach (var node in function.Descendants.OfType<TryFinally>())
  47. {
  48. EliminateRedundantTryFinally(node, context);
  49. }
  50. }
  51. private void Visit(BlockContainer container, Block continueTarget)
  52. {
  53. switch (container.Kind)
  54. {
  55. case ContainerKind.Loop:
  56. case ContainerKind.While:
  57. continueTarget = container.EntryPoint;
  58. break;
  59. case ContainerKind.DoWhile:
  60. case ContainerKind.For:
  61. continueTarget = container.Blocks.Last();
  62. break;
  63. }
  64. for (int i = 0; i < container.Blocks.Count; i++)
  65. {
  66. var block = container.Blocks[i];
  67. // Note: it's possible for additional blocks to be appended to the container
  68. // by the Visit() call; but there should be no other changes to the Blocks collection.
  69. Visit(block, continueTarget);
  70. Debug.Assert(container.Blocks[i] == block);
  71. }
  72. }
  73. /// <summary>
  74. /// Visits a block in context
  75. /// </summary>
  76. /// <param name="block"></param>
  77. /// <param name="continueTarget">Marks the target block of continue statements.</param>
  78. /// <param name="nextInstruction">The instruction following the end point of the block. Can only be null if the end point is unreachable.</param>
  79. private void Visit(Block block, Block continueTarget, ILInstruction nextInstruction = null)
  80. {
  81. Debug.Assert(block.HasFlag(InstructionFlags.EndPointUnreachable) || nextInstruction != null);
  82. // process each instruction in the block.
  83. for (int i = 0; i < block.Instructions.Count; i++)
  84. {
  85. // Transformations may be applied to the current and following instructions but already processed instructions will not be changed
  86. var inst = block.Instructions[i];
  87. // the next instruction to be executed. Transformations will change the next instruction, so this is a method instead of a variable
  88. ILInstruction NextInsn() => block.Instructions.ElementAtOrDefault(i + 1) ?? nextInstruction;
  89. switch (inst)
  90. {
  91. case BlockContainer container:
  92. // visit the contents of the container
  93. Visit(container, continueTarget);
  94. // reduce nesting in switch blocks
  95. if (container.Kind == ContainerKind.Switch &&
  96. CanDuplicateExit(NextInsn(), continueTarget, out var keywordExit1) &&
  97. ReduceSwitchNesting(block, container, keywordExit1))
  98. {
  99. RemoveRedundantExit(block, nextInstruction);
  100. }
  101. break;
  102. case IfInstruction ifInst:
  103. ImproveILOrdering(block, ifInst, continueTarget);
  104. // reduce nesting in if/else blocks
  105. if (CanDuplicateExit(NextInsn(), continueTarget, out var keywordExit2) && ReduceNesting(block, ifInst, keywordExit2))
  106. RemoveRedundantExit(block, nextInstruction);
  107. // visit content blocks
  108. if (ifInst.TrueInst is Block trueBlock)
  109. Visit(trueBlock, continueTarget, NextInsn());
  110. if (ifInst.FalseInst is Block falseBlock)
  111. {
  112. if (ifInst.TrueInst.HasFlag(InstructionFlags.EndPointUnreachable))
  113. {
  114. ExtractElseBlock(ifInst);
  115. break;
  116. }
  117. Visit(falseBlock, continueTarget, NextInsn());
  118. }
  119. break;
  120. default:
  121. // blocks can only exit containers via leave instructions, not fallthrough, so the only relevant context is `continueTarget`
  122. VisitContainers(inst, continueTarget);
  123. // reducing nesting inside Try/Using/Lock etc, may make the endpoint unreachable.
  124. // This should only happen by replacing a Leave with the exit instruction we're about to delete, but I can't see a good way to assert this
  125. // This would be better placed in ReduceNesting, but it's more difficult to find the affected instructions/blocks there than here
  126. if (i == block.Instructions.Count - 2 && inst.HasFlag(InstructionFlags.EndPointUnreachable))
  127. {
  128. context.Step("Remove unreachable exit", block.Instructions.Last());
  129. block.Instructions.RemoveLast();
  130. // This would be the right place to check and fix the redundant continue; in TestCases.Pretty.ReduceNesting.BreakLockInLoop
  131. // but doing so would require knowledge of what `inst` is, and how it works. (eg. to target the try block and not catch or finally blocks)
  132. }
  133. break;
  134. }
  135. }
  136. }
  137. // search for child containers to reduce nesting in
  138. private void VisitContainers(ILInstruction inst, Block continueTarget)
  139. {
  140. switch (inst)
  141. {
  142. case ILFunction _:
  143. break; // assume inline ILFunctions are already transformed
  144. case BlockContainer cont:
  145. Visit(cont, continueTarget);
  146. break;
  147. default:
  148. foreach (var child in inst.Children)
  149. VisitContainers(child, continueTarget);
  150. break;
  151. }
  152. }
  153. /// <summary>
  154. /// For an if statement with an unreachable end point and no else block,
  155. /// inverts to match IL order of the first statement of each branch
  156. /// </summary>
  157. private void ImproveILOrdering(Block block, IfInstruction ifInst, Block continueTarget)
  158. {
  159. if (!block.HasFlag(InstructionFlags.EndPointUnreachable)
  160. || !ifInst.TrueInst.HasFlag(InstructionFlags.EndPointUnreachable)
  161. || !ifInst.FalseInst.MatchNop())
  162. return;
  163. Debug.Assert(ifInst != block.Instructions.Last());
  164. var trueRangeStart = ConditionDetection.GetStartILOffset(ifInst.TrueInst, out bool trueRangeIsEmpty);
  165. var falseRangeStart = ConditionDetection.GetStartILOffset(block.Instructions[block.Instructions.IndexOf(ifInst) + 1], out bool falseRangeIsEmpty);
  166. if (trueRangeIsEmpty || falseRangeIsEmpty || falseRangeStart >= trueRangeStart)
  167. return;
  168. if (block.Instructions.Last() is Leave leave && !leave.IsLeavingFunction && leave.TargetContainer.Kind == ContainerKind.Normal)
  169. {
  170. // non-keyword leave. Can't move out of the last position in the block (fall-through) without introducing goto, unless it can be replaced with a keyword (return/continue)
  171. if (!CanDuplicateExit(block.Instructions.Last(), continueTarget, out var keywordExit))
  172. return;
  173. context.Step("Replace leave with keyword exit", ifInst.TrueInst);
  174. block.Instructions.Last().ReplaceWith(keywordExit.Clone());
  175. }
  176. ConditionDetection.InvertIf(block, ifInst, context);
  177. }
  178. /// <summary>
  179. /// Reduce Nesting in if/else statements by duplicating an exit instruction.
  180. /// Does not affect IL order
  181. /// </summary>
  182. private bool ReduceNesting(Block block, IfInstruction ifInst, ILInstruction exitInst)
  183. {
  184. // start tallying stats for heuristics from then and else-if blocks
  185. int maxStatements = 0, maxDepth = 0;
  186. UpdateStats(ifInst.TrueInst, ref maxStatements, ref maxDepth);
  187. // if (cond) { ... } exit;
  188. if (ifInst.FalseInst.MatchNop())
  189. {
  190. // a separate heuristic to ShouldReduceNesting as there is visual balancing to be performed based on number of statments
  191. if (maxDepth < 2)
  192. return false;
  193. // ->
  194. // if (!cond) exit;
  195. // ...; exit;
  196. EnsureEndPointUnreachable(block, exitInst);
  197. Debug.Assert(ifInst == block.Instructions.SecondToLastOrDefault());
  198. // use the same exit the block has. If the block already has one (such as a leave from a try), keep it in place
  199. EnsureEndPointUnreachable(ifInst.TrueInst, block.Instructions.Last());
  200. ConditionDetection.InvertIf(block, ifInst, context);
  201. // ensure the exit inst of the if instruction is a keyword
  202. Debug.Assert(!(ifInst.TrueInst is Block));
  203. if (!ifInst.TrueInst.Match(exitInst).Success)
  204. {
  205. Debug.Assert(ifInst.TrueInst is Leave);
  206. context.Step("Replace leave with keyword exit", ifInst.TrueInst);
  207. ifInst.TrueInst.ReplaceWith(exitInst.Clone());
  208. }
  209. return true;
  210. }
  211. // else-if trees are considered as a single group from the root IfInstruction
  212. if (GetElseIfParent(ifInst) != null)
  213. return false;
  214. // find the else block and tally stats for each else-if block
  215. while (Block.Unwrap(ifInst.FalseInst) is IfInstruction elseIfInst)
  216. {
  217. UpdateStats(elseIfInst.TrueInst, ref maxStatements, ref maxDepth);
  218. ifInst = elseIfInst;
  219. }
  220. if (!ShouldReduceNesting(ifInst.FalseInst, maxStatements, maxDepth))
  221. return false;
  222. // extract the else block and insert exit points all the way up the else-if tree
  223. do
  224. {
  225. var elseIfInst = GetElseIfParent(ifInst);
  226. // if (cond) { ... } else { ... } exit;
  227. // ->
  228. // if (cond) { ...; exit; }
  229. // ...; exit;
  230. EnsureEndPointUnreachable(ifInst.TrueInst, exitInst);
  231. if (ifInst.FalseInst.HasFlag(InstructionFlags.EndPointUnreachable))
  232. {
  233. Debug.Assert(ifInst.HasFlag(InstructionFlags.EndPointUnreachable));
  234. Debug.Assert(ifInst.Parent == block);
  235. int removeAfter = ifInst.ChildIndex + 1;
  236. if (removeAfter < block.Instructions.Count)
  237. {
  238. // Remove all instructions that ended up dead
  239. // (this should just be exitInst itself)
  240. Debug.Assert(block.Instructions.SecondToLastOrDefault() == ifInst);
  241. Debug.Assert(block.Instructions.Last() == exitInst);
  242. block.Instructions.RemoveRange(removeAfter, block.Instructions.Count - removeAfter);
  243. }
  244. }
  245. ExtractElseBlock(ifInst);
  246. ifInst = elseIfInst;
  247. } while (ifInst != null);
  248. return true;
  249. }
  250. /// <summary>
  251. /// Reduce Nesting in switch statements by replacing break; in cases with the block exit, and extracting the default case
  252. /// Does not affect IL order
  253. /// </summary>
  254. private bool ReduceSwitchNesting(Block parentBlock, BlockContainer switchContainer, ILInstruction exitInst)
  255. {
  256. // break; from outer container cannot be brought inside the switch as the meaning would change
  257. if (exitInst is Leave leave && !leave.IsLeavingFunction)
  258. return false;
  259. // find the default section, and ensure it has only one incoming edge
  260. var switchInst = (SwitchInstruction)switchContainer.EntryPoint.Instructions.Single();
  261. var defaultSection = switchInst.Sections.MaxBy(s => s.Labels.Count());
  262. if (!defaultSection.Body.MatchBranch(out var defaultBlock) || defaultBlock.IncomingEdgeCount != 1)
  263. return false;
  264. if (defaultBlock.Parent != switchContainer)
  265. return false;
  266. // tally stats for heuristic from each case block
  267. int maxStatements = 0, maxDepth = 0;
  268. foreach (var section in switchInst.Sections)
  269. if (section != defaultSection && section.Body.MatchBranch(out var caseBlock) && caseBlock.Parent == switchContainer)
  270. UpdateStats(caseBlock, ref maxStatements, ref maxDepth);
  271. if (!ShouldReduceNesting(defaultBlock, maxStatements, maxDepth))
  272. return false;
  273. Debug.Assert(defaultBlock.HasFlag(InstructionFlags.EndPointUnreachable));
  274. // ensure the default case dominator tree has no exits (branches to other cases)
  275. var cfg = new ControlFlowGraph(switchContainer, context.CancellationToken);
  276. var defaultNode = cfg.GetNode(defaultBlock);
  277. var defaultTree = TreeTraversal.PreOrder(defaultNode, n => n.DominatorTreeChildren).ToList();
  278. if (defaultTree.SelectMany(n => n.Successors).Any(n => !defaultNode.Dominates(n)))
  279. return false;
  280. if (defaultTree.Count > 1 && !(parentBlock.Parent is BlockContainer))
  281. return false;
  282. context.Step("Extract default case of switch", switchContainer);
  283. // if the switch container is followed by an instruction, it must be a Leave from a try/pinned/etc or exitInst
  284. // When it's a leave from a container, it's better to let the extracted default block 'fall through' rather than duplicating whatever
  285. // instruction eventually follows the container
  286. if (parentBlock.Instructions.SecondToLastOrDefault() == switchContainer)
  287. {
  288. if (defaultBlock.Instructions.Last().MatchLeave(switchContainer))
  289. defaultBlock.Instructions.Last().ReplaceWith(parentBlock.Instructions.Last());
  290. parentBlock.Instructions.RemoveLast();
  291. }
  292. // replace all break; statements with the exitInst
  293. var leaveInstructions = switchContainer.Descendants.Where(inst => inst.MatchLeave(switchContainer));
  294. foreach (var leaveInst in leaveInstructions.ToArray())
  295. leaveInst.ReplaceWith(exitInst.Clone());
  296. // replace the default section branch with a break;
  297. defaultSection.Body.ReplaceWith(new Leave(switchContainer));
  298. // remove all default blocks from the switch container
  299. var defaultBlocks = defaultTree.Select(c => (Block)c.UserData).ToList();
  300. foreach (var block in defaultBlocks)
  301. switchContainer.Blocks.Remove(block);
  302. Debug.Assert(parentBlock.Instructions.Last() == switchContainer);
  303. parentBlock.Instructions.AddRange(defaultBlock.Instructions);
  304. // add any additional blocks from the default case to the parent container
  305. Debug.Assert(defaultBlocks[0] == defaultBlock);
  306. if (defaultBlocks.Count > 1)
  307. {
  308. var parentContainer = (BlockContainer)parentBlock.Parent;
  309. int insertAt = parentContainer.Blocks.IndexOf(parentBlock) + 1;
  310. foreach (var block in defaultBlocks.Skip(1))
  311. parentContainer.Blocks.Insert(insertAt++, block);
  312. }
  313. return true;
  314. }
  315. /// <summary>
  316. /// Checks if an exit is a duplicable keyword exit (return; break; continue;)
  317. /// </summary>
  318. private bool CanDuplicateExit(ILInstruction exit, Block continueTarget, out ILInstruction keywordExit)
  319. {
  320. keywordExit = exit;
  321. if (exit != null && exit.MatchBranch(continueTarget))
  322. return true; // keyword is continue
  323. if (!(exit is Leave leave && leave.Value.MatchNop()))
  324. return false; // don't duplicate valued returns
  325. if (leave.IsLeavingFunction || leave.TargetContainer.Kind != ContainerKind.Normal)
  326. return true; // keyword is return || break
  327. // leave from a try/pinned/lock etc, check if the target (the instruction following the target container) is duplicable, if so, set keywordExit to that
  328. ILInstruction leavingInst = leave.TargetContainer;
  329. Debug.Assert(!leavingInst.HasFlag(InstructionFlags.EndPointUnreachable));
  330. while (!(leavingInst.Parent is Block b) || leavingInst == b.Instructions.Last())
  331. {
  332. if (leavingInst.Parent is TryFinally tryFinally)
  333. {
  334. if (leavingInst.SlotInfo == TryFinally.FinallyBlockSlot)
  335. { // cannot duplicate leaves from finally containers
  336. Debug.Assert(leave.TargetContainer == tryFinally.FinallyBlock); //finally cannot have control flow
  337. return false;
  338. }
  339. if (tryFinally.HasFlag(InstructionFlags.EndPointUnreachable))
  340. { // finally block changes return value/throws an exception? Yikes. Lets leave it alone
  341. Debug.Assert(tryFinally.FinallyBlock.HasFlag(InstructionFlags.EndPointUnreachable));
  342. return false;
  343. }
  344. }
  345. else if (leavingInst.Parent is TryFault tryFault && leavingInst.SlotInfo == TryFault.FaultBlockSlot)
  346. { // cannot duplicate leaves from fault containers either
  347. Debug.Assert(leave.TargetContainer == tryFault.FaultBlock);
  348. return false;
  349. }
  350. leavingInst = leavingInst.Parent;
  351. Debug.Assert(!leavingInst.HasFlag(InstructionFlags.EndPointUnreachable));
  352. Debug.Assert(!(leavingInst is ILFunction));
  353. }
  354. var block = (Block)leavingInst.Parent;
  355. var targetInst = block.Instructions[block.Instructions.IndexOf(leavingInst) + 1];
  356. return CanDuplicateExit(targetInst, continueTarget, out keywordExit);
  357. }
  358. /// <summary>
  359. /// Ensures the end point of a block is unreachable by duplicating and appending the [exit] instruction following the end point
  360. /// </summary>
  361. /// <param name="inst">The instruction/block of interest</param>
  362. /// <param name="fallthroughExit">The next instruction to be executed (provided inst does not exit)</param>
  363. private void EnsureEndPointUnreachable(ILInstruction inst, ILInstruction fallthroughExit)
  364. {
  365. if (!(inst is Block block))
  366. {
  367. Debug.Assert(inst.HasFlag(InstructionFlags.EndPointUnreachable));
  368. return;
  369. }
  370. if (!block.HasFlag(InstructionFlags.EndPointUnreachable))
  371. {
  372. context.Step("Duplicate block exit", fallthroughExit);
  373. block.Instructions.Add(fallthroughExit.Clone());
  374. }
  375. }
  376. /// <summary>
  377. /// Removes a redundant block exit instruction.
  378. /// </summary>
  379. private void RemoveRedundantExit(Block block, ILInstruction implicitExit)
  380. {
  381. if (block.Instructions.Last().Match(implicitExit).Success)
  382. {
  383. context.Step("Remove redundant exit", block.Instructions.Last());
  384. block.Instructions.RemoveLast();
  385. }
  386. }
  387. /// <summary>
  388. /// Determines if an IfInstruction is an else-if and returns the preceeding (parent) IfInstruction
  389. ///
  390. /// [else-]if (parent-cond) else { ifInst }
  391. /// </summary>
  392. private IfInstruction GetElseIfParent(IfInstruction ifInst)
  393. {
  394. Debug.Assert(ifInst.Parent is Block);
  395. if (Block.Unwrap(ifInst.Parent) == ifInst && // only instruction in block
  396. ifInst.Parent.Parent is IfInstruction elseIfInst && // parent of block is an IfInstruction
  397. elseIfInst.FalseInst == ifInst.Parent) // part of the false branch not the true branch
  398. return elseIfInst;
  399. return null;
  400. }
  401. /// <summary>
  402. /// Adds a code path to the current heuristic tally
  403. /// </summary>
  404. private void UpdateStats(ILInstruction inst, ref int maxStatements, ref int maxDepth)
  405. {
  406. int numStatements = 0;
  407. ComputeStats(inst, ref numStatements, ref maxDepth, 0);
  408. maxStatements = Math.Max(numStatements, maxStatements);
  409. }
  410. /// <summary>
  411. /// Recursively computes the number of statements and maximum nested depth of an instruction
  412. /// </summary>
  413. private void ComputeStats(ILInstruction inst, ref int numStatements, ref int maxDepth, int currentDepth, bool isStatement = true)
  414. {
  415. if (isStatement)
  416. numStatements++;
  417. if (currentDepth > maxDepth)
  418. {
  419. Debug.Assert(isStatement);
  420. maxDepth = currentDepth;
  421. }
  422. // enumerate children statements and containers
  423. switch (inst)
  424. {
  425. case Block block:
  426. if (isStatement)
  427. numStatements--; // don't count blocks as statements
  428. // add each child as a statement (unless we're a named block)
  429. foreach (var child in block.Instructions)
  430. ComputeStats(child, ref numStatements, ref maxDepth, currentDepth, block.Kind != BlockKind.CallWithNamedArgs && block.Kind != BlockKind.CallInlineAssign);
  431. // final instruction as an expression
  432. ComputeStats(block.FinalInstruction, ref numStatements, ref maxDepth, currentDepth, false);
  433. break;
  434. case BlockContainer container:
  435. if (!isStatement)
  436. numStatements++; //always add a statement for a container in an expression
  437. var containerBody = container.EntryPoint;
  438. if (container.Kind == ContainerKind.For || container.Kind == ContainerKind.While)
  439. {
  440. Debug.Assert(isStatement);
  441. if (!container.MatchConditionBlock(container.EntryPoint, out _, out containerBody))
  442. throw new NotSupportedException("Invalid condition block in loop.");
  443. }
  444. // don't count implicit leave. Can't avoid counting for loop initializers but close enough, for loops can have an extra statement of visual weight
  445. var lastInst = containerBody.Instructions.Last();
  446. if ((container.Kind == ContainerKind.For || container.Kind == ContainerKind.DoWhile) && lastInst.MatchBranch(container.Blocks.Last()) ||
  447. (container.Kind == ContainerKind.Loop || container.Kind == ContainerKind.While) && lastInst.MatchBranch(container.Blocks[0]) ||
  448. container.Kind == ContainerKind.Normal && lastInst.MatchLeave(container) ||
  449. container.Kind == ContainerKind.Switch) // SwitchInstructyion always counts as a statement anyway, so no need to count the container as well
  450. numStatements--;
  451. // add the nested body
  452. ComputeStats(containerBody, ref numStatements, ref maxDepth, currentDepth + 1);
  453. break;
  454. case IfInstruction ifInst when ifInst.ResultType == StackType.Void:
  455. Debug.Assert(isStatement);
  456. // nested then instruction
  457. ComputeStats(ifInst.TrueInst, ref numStatements, ref maxDepth, currentDepth + 1);
  458. // include all nested else-if instructions at the same depth
  459. var elseInst = ifInst.FalseInst;
  460. while (Block.Unwrap(elseInst) is IfInstruction elseIfInst)
  461. {
  462. numStatements++;
  463. ComputeStats(elseIfInst.TrueInst, ref numStatements, ref maxDepth, currentDepth + 1);
  464. elseInst = elseIfInst.FalseInst;
  465. }
  466. // include all nested else instruction
  467. ComputeStats(elseInst, ref numStatements, ref maxDepth, currentDepth + 1);
  468. break;
  469. case SwitchSection section:
  470. Debug.Assert(!isStatement); // labels are just children of the SwitchInstruction
  471. numStatements++; // add a statement for each case label
  472. // add all the case blocks at the current depth
  473. // most formatters indent switch blocks twice, but we don't want this heuristic to be based on formatting
  474. // so we remain conservative and only include the increase in depth from the container and not the labels
  475. if (section.Body.MatchBranch(out var caseBlock) && caseBlock.Parent == section.Parent.Parent.Parent)
  476. ComputeStats(caseBlock, ref numStatements, ref maxDepth, currentDepth);
  477. break;
  478. case ILFunction func:
  479. int bodyStatements = 0;
  480. int bodyMaxDepth = maxDepth;
  481. ComputeStats(func.Body, ref bodyStatements, ref bodyMaxDepth, currentDepth);
  482. if (bodyStatements >= 2)
  483. { // don't count inline functions
  484. numStatements += bodyStatements;
  485. maxDepth = bodyMaxDepth;
  486. }
  487. break;
  488. default:
  489. // search each child instruction. Containers will contain statements and contribute to stats
  490. int subStatements = 0;
  491. foreach (var child in inst.Children)
  492. ComputeStats(child, ref subStatements, ref maxDepth, currentDepth, false);
  493. numStatements += subStatements;
  494. if (isStatement && subStatements > 0)
  495. numStatements--; // don't count the first container, only its contents, because this statement is already counted
  496. break;
  497. }
  498. }
  499. /// <summary>
  500. /// Heuristic to determine whether it is worth duplicating exits into the preceeding sibling blocks (then/else-if/case)
  501. /// in order to reduce the nesting of inst by 1
  502. /// </summary>
  503. /// <param name="inst">The instruction heading the nested candidate block</param>
  504. /// <param name="maxStatements">The number of statements in the largest sibling block</param>
  505. /// <param name="maxDepth">The relative depth of the most nested statement in the sibling blocks</param>
  506. /// <returns></returns>
  507. private bool ShouldReduceNesting(ILInstruction inst, int maxStatements, int maxDepth)
  508. {
  509. int maxStatements2 = 0, maxDepth2 = 0;
  510. UpdateStats(inst, ref maxStatements2, ref maxDepth2);
  511. // if the max depth is 2, always reduce nesting (total depth 3 or more)
  512. // if the max depth is 1, reduce nesting if this block is the largest
  513. // otherwise reduce nesting only if this block is twice as large as any other
  514. return maxDepth2 >= 2 || maxDepth2 >= 1 && maxStatements2 > maxStatements || maxStatements2 >= 2 * maxStatements;
  515. }
  516. /// <summary>
  517. /// if (cond) { ...; exit; } else { ... }
  518. /// ...;
  519. /// ->
  520. /// if (cond) { ...; exit; }
  521. /// ...;
  522. /// ...;
  523. /// </summary>
  524. /// <param name="ifInst"></param>
  525. private void ExtractElseBlock(IfInstruction ifInst)
  526. {
  527. Debug.Assert(ifInst.TrueInst.HasFlag(InstructionFlags.EndPointUnreachable));
  528. var block = (Block)ifInst.Parent;
  529. var falseBlock = (Block)ifInst.FalseInst;
  530. context.Step("Extract else block", ifInst);
  531. int insertAt = block.Instructions.IndexOf(ifInst) + 1;
  532. for (int i = 0; i < falseBlock.Instructions.Count; i++)
  533. block.Instructions.Insert(insertAt++, falseBlock.Instructions[i]);
  534. ifInst.FalseInst = new Nop();
  535. }
  536. private void EliminateRedundantTryFinally(TryFinally tryFinally, ILTransformContext context)
  537. {
  538. /* The C# compiler sometimes generates try-finally structures for fixed statements.
  539. After our transforms runs, these are redundant and can be removed.
  540. .try BlockContainer {
  541. Block IL_001a (incoming: 1) {
  542. PinnedRegion ...
  543. }
  544. } finally BlockContainer {
  545. Block IL_003e (incoming: 1) {
  546. leave IL_003e (nop)
  547. }
  548. }
  549. ==> PinnedRegion
  550. */
  551. if (!(tryFinally.FinallyBlock is BlockContainer finallyContainer))
  552. return;
  553. if (!finallyContainer.SingleInstruction().MatchLeave(finallyContainer))
  554. return;
  555. // Finally is empty and redundant. But we'll delete the block only if there's a PinnedRegion.
  556. if (!(tryFinally.TryBlock is BlockContainer tryContainer))
  557. return;
  558. if (tryContainer.Blocks.Count != 1)
  559. return;
  560. var tryBlock = tryContainer.Blocks[0];
  561. if (tryBlock.Instructions.Count == 1)
  562. {
  563. if (tryBlock.Instructions[0] is PinnedRegion pinnedRegion)
  564. {
  565. context.Step("Removing try-finally around PinnedRegion", pinnedRegion);
  566. tryFinally.ReplaceWith(pinnedRegion);
  567. }
  568. }
  569. else if (tryBlock.Instructions.Count == 2)
  570. {
  571. if (tryBlock.Instructions[0] is PinnedRegion pinnedRegion &&
  572. tryBlock.Instructions[1].MatchLeave(tryContainer))
  573. {
  574. context.Step("Removing try-finally around PinnedRegion", pinnedRegion);
  575. tryFinally.ReplaceWith(pinnedRegion);
  576. }
  577. }
  578. }
  579. }
  580. }