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.

520 lines
18 KiB

8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
  1. // Copyright (c) 2017 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.Collections.Generic;
  20. using System.Diagnostics;
  21. using System.Linq;
  22. using ICSharpCode.Decompiler.IL.ControlFlow;
  23. using ICSharpCode.Decompiler.Util;
  24. namespace ICSharpCode.Decompiler.IL.Transforms
  25. {
  26. /// <summary>
  27. /// If possible, transforms plain ILAst loops into while (condition), do-while and for-loops.
  28. /// For the invariants of the transforms <see cref="BlockContainer.CheckInvariant(ILPhase)"/>.
  29. /// </summary>
  30. public class HighLevelLoopTransform : IILTransform
  31. {
  32. ILTransformContext context;
  33. public void Run(ILFunction function, ILTransformContext context)
  34. {
  35. this.context = context;
  36. foreach (BlockContainer loop in function.Descendants.OfType<BlockContainer>())
  37. {
  38. if (loop.Kind != ContainerKind.Loop)
  39. continue;
  40. if (MatchWhileLoop(loop, out var condition, out var loopBody))
  41. {
  42. if (context.Settings.ForStatement)
  43. MatchForLoop(loop, condition, loopBody);
  44. continue;
  45. }
  46. if (context.Settings.DoWhileStatement && MatchDoWhileLoop(loop))
  47. continue;
  48. }
  49. }
  50. bool MatchWhileLoop(BlockContainer loop, out IfInstruction condition, out Block loopBody)
  51. {
  52. // ConditionDetection favours leave inside if and branch at end of block
  53. // while-loop:
  54. // if (!loop-condition) leave loop-container
  55. // ...
  56. condition = null;
  57. loopBody = loop.EntryPoint;
  58. if (!(loopBody.Instructions[0] is IfInstruction ifInstruction))
  59. return false;
  60. if (!ifInstruction.FalseInst.MatchNop())
  61. return false;
  62. if (UsesVariableCapturedInLoop(loop, ifInstruction.Condition))
  63. return false;
  64. condition = ifInstruction;
  65. if (!ifInstruction.TrueInst.MatchLeave(loop))
  66. {
  67. // sometimes the loop-body is nested within the if
  68. // if (loop-condition) { loop-body }
  69. // leave loop-container
  70. if (loopBody.Instructions.Count != 2 || !loop.EntryPoint.Instructions.Last().MatchLeave(loop))
  71. return false;
  72. if (!ifInstruction.TrueInst.HasFlag(InstructionFlags.EndPointUnreachable))
  73. ((Block)ifInstruction.TrueInst).Instructions.Add(new Leave(loop));
  74. ConditionDetection.InvertIf(loopBody, ifInstruction, context);
  75. }
  76. context.Step("Transform to while (condition) loop: " + loop.EntryPoint.Label, loop);
  77. loop.Kind = ContainerKind.While;
  78. //invert comparison
  79. ifInstruction.Condition = Comp.LogicNot(ifInstruction.Condition);
  80. ifInstruction.FalseInst = ifInstruction.TrueInst;
  81. //move the rest of the body into a new block
  82. loopBody = new Block();
  83. loopBody.AddRef();
  84. ConditionDetection.ExtractBlock(loop.EntryPoint, 1, loop.EntryPoint.Instructions.Count, loopBody);
  85. loop.Blocks.Insert(1, loopBody);
  86. loopBody.ReleaseRef();
  87. if (!loopBody.HasFlag(InstructionFlags.EndPointUnreachable))
  88. loopBody.Instructions.Add(new Leave(loop));
  89. ifInstruction.TrueInst = new Branch(loopBody);
  90. ExpressionTransforms.RunOnSingleStatement(ifInstruction, context);
  91. // Analyze conditions and decide whether to move some of them out of the condition block:
  92. /*var conditions = new List<ILInstruction>();
  93. SplitConditions(condition.Condition, conditions);
  94. // Break apart conditions that could be a MoveNext call followed by a Current accessor call:
  95. if (MightBeHeaderOfForEach(loop, conditions)) {
  96. ifInstruction.Condition = conditions[0];
  97. foreach (var cond in conditions.Skip(1).Reverse()) {
  98. IfInstruction inst;
  99. loopBody.Instructions.Insert(0, inst = new IfInstruction(Comp.LogicNot(cond), new Leave(loop)));
  100. ExpressionTransforms.RunOnSingleStatment(inst, context);
  101. }
  102. }*/
  103. return true;
  104. }
  105. bool MightBeHeaderOfForEach(BlockContainer loop, List<ILInstruction> conditions)
  106. {
  107. if (conditions.Count <= 1)
  108. return false;
  109. if (!(conditions[0] is CallInstruction moveNextCall && moveNextCall.Method.Name == "MoveNext"
  110. && conditions[1].Descendants.Any(IsGetCurrentCall)))
  111. return false;
  112. return loop.Parent?.Parent?.Parent is UsingInstruction;
  113. bool IsGetCurrentCall(ILInstruction inst)
  114. {
  115. return inst is CallInstruction getterCall
  116. && getterCall.Method.IsAccessor
  117. && getterCall.Method.Name == "get_Current";
  118. }
  119. }
  120. void SplitConditions(ILInstruction expression, List<ILInstruction> conditions)
  121. {
  122. if (expression.MatchLogicAnd(out var l, out var r))
  123. {
  124. SplitConditions(l, conditions);
  125. SplitConditions(r, conditions);
  126. }
  127. else
  128. {
  129. conditions.Add(expression);
  130. }
  131. }
  132. /// <summary>
  133. /// Matches a do-while loop and performs the following transformations:
  134. /// - combine all compatible conditions into one IfInstruction.
  135. /// - extract conditions into a condition block, or move the existing condition block to the end.
  136. /// </summary>
  137. bool MatchDoWhileLoop(BlockContainer loop)
  138. {
  139. (List<IfInstruction> conditions, ILInstruction exit, bool swap, bool split, bool unwrap) = AnalyzeDoWhileConditions(loop);
  140. // not a do-while loop, exit.
  141. if (conditions == null || conditions.Count == 0)
  142. return false;
  143. context.Step("Transform to do-while loop: " + loop.EntryPoint.Label, loop);
  144. Block conditionBlock;
  145. // first we remove all extracted instructions from the original block.
  146. var originalBlock = (Block)exit.Parent;
  147. if (unwrap)
  148. {
  149. // we found a condition block nested in a condition that is followed by a return statement:
  150. // we flip the condition and swap the blocks
  151. Debug.Assert(originalBlock.Parent is IfInstruction);
  152. var returnCondition = (IfInstruction)originalBlock.Parent;
  153. var topLevelBlock = (Block)returnCondition.Parent;
  154. Debug.Assert(topLevelBlock.Parent == loop);
  155. var leaveFunction = topLevelBlock.Instructions[returnCondition.ChildIndex + 1];
  156. Debug.Assert(leaveFunction.MatchReturn(out _));
  157. returnCondition.Condition = Comp.LogicNot(returnCondition.Condition);
  158. returnCondition.TrueInst = leaveFunction;
  159. // simplify the condition:
  160. ExpressionTransforms.RunOnSingleStatement(returnCondition, context);
  161. topLevelBlock.Instructions.RemoveAt(returnCondition.ChildIndex + 1);
  162. topLevelBlock.Instructions.AddRange(originalBlock.Instructions);
  163. originalBlock = topLevelBlock;
  164. split = true;
  165. }
  166. originalBlock.Instructions.RemoveRange(originalBlock.Instructions.Count - conditions.Count - 1, conditions.Count + 1);
  167. // we need to split the block:
  168. if (split)
  169. {
  170. // add a new block at the end and add a branch to the new block.
  171. conditionBlock = new Block();
  172. loop.Blocks.Add(conditionBlock);
  173. originalBlock.Instructions.Add(new Branch(conditionBlock));
  174. }
  175. else
  176. {
  177. // move the condition block to the end.
  178. conditionBlock = originalBlock;
  179. loop.Blocks.MoveElementToEnd(originalBlock);
  180. }
  181. // combine all conditions and the exit instruction into one IfInstruction:
  182. IfInstruction condition = null;
  183. conditionBlock.AddILRange(exit);
  184. foreach (var inst in conditions)
  185. {
  186. conditionBlock.AddILRange(inst);
  187. if (condition == null)
  188. {
  189. condition = inst;
  190. if (swap)
  191. {
  192. // branches must be swapped and condition negated:
  193. condition.Condition = Comp.LogicNot(condition.Condition);
  194. condition.FalseInst = condition.TrueInst;
  195. condition.TrueInst = exit;
  196. }
  197. else
  198. {
  199. condition.FalseInst = exit;
  200. }
  201. }
  202. else
  203. {
  204. if (swap)
  205. {
  206. condition.Condition = IfInstruction.LogicAnd(Comp.LogicNot(inst.Condition), condition.Condition);
  207. }
  208. else
  209. {
  210. condition.Condition = IfInstruction.LogicAnd(inst.Condition, condition.Condition);
  211. }
  212. }
  213. }
  214. // insert the combined conditions into the condition block:
  215. conditionBlock.Instructions.Add(condition);
  216. // simplify the condition:
  217. ExpressionTransforms.RunOnSingleStatement(condition, context);
  218. // transform complete
  219. loop.Kind = ContainerKind.DoWhile;
  220. return true;
  221. }
  222. static (List<IfInstruction> conditions, ILInstruction exit, bool swap, bool split, bool unwrap) AnalyzeDoWhileConditions(BlockContainer loop)
  223. {
  224. // we iterate over all blocks from the bottom, because the entry-point
  225. // should only be considered as condition block, if there are no other blocks.
  226. foreach (var block in loop.Blocks.Reverse())
  227. {
  228. // first we match the end of the block:
  229. if (MatchDoWhileConditionBlock(loop, block, out bool swap, out bool unwrapCondtionBlock, out Block conditionBlock))
  230. {
  231. // now collect all instructions that are usable as loop conditions
  232. var conditions = CollectConditions(loop, conditionBlock, swap);
  233. // split only if the block is either the entry-point or contains other instructions as well.
  234. var split = conditionBlock == loop.EntryPoint || conditionBlock.Instructions.Count > conditions.Count + 1; // + 1 is the final leave/branch.
  235. return (conditions, conditionBlock.Instructions.Last(), swap, split, unwrapCondtionBlock);
  236. }
  237. }
  238. return (null, null, false, false, false);
  239. }
  240. /// <summary>
  241. /// Returns a list of all IfInstructions that can be used as loop conditon, i.e.,
  242. /// that have no false-instruction and have leave loop (if swapped) or branch entry-point as true-instruction.
  243. /// </summary>
  244. static List<IfInstruction> CollectConditions(BlockContainer loop, Block block, bool swap)
  245. {
  246. var list = new List<IfInstruction>();
  247. int i = block.Instructions.Count - 2;
  248. while (i >= 0 && block.Instructions[i] is IfInstruction ifInst)
  249. {
  250. if (!ifInst.FalseInst.MatchNop())
  251. break;
  252. if (UsesVariableCapturedInLoop(loop, ifInst.Condition))
  253. break;
  254. if (swap)
  255. {
  256. if (!ifInst.TrueInst.MatchLeave(loop))
  257. break;
  258. list.Add(ifInst);
  259. }
  260. else
  261. {
  262. if (!ifInst.TrueInst.MatchBranch(loop.EntryPoint))
  263. break;
  264. list.Add(ifInst);
  265. }
  266. i--;
  267. }
  268. return list;
  269. }
  270. static bool UsesVariableCapturedInLoop(BlockContainer loop, ILInstruction condition)
  271. {
  272. foreach (var inst in condition.Descendants.OfType<IInstructionWithVariableOperand>())
  273. {
  274. if (inst.Variable.CaptureScope == loop)
  275. return true;
  276. }
  277. return false;
  278. }
  279. static bool MatchDoWhileConditionBlock(BlockContainer loop, Block block, out bool swapBranches, out bool unwrapCondtionBlock, out Block conditionBlock)
  280. {
  281. // match the end of the block:
  282. // if (condition) branch entry-point else nop
  283. // leave loop
  284. // -or-
  285. // if (condition) leave loop else nop
  286. // branch entry-point
  287. swapBranches = false;
  288. unwrapCondtionBlock = false;
  289. conditionBlock = block;
  290. // empty block?
  291. if (block.Instructions.Count < 2)
  292. return false;
  293. var last = block.Instructions.Last();
  294. var ifInstruction = block.Instructions.SecondToLastOrDefault() as IfInstruction;
  295. // no IfInstruction or already transformed?
  296. if (ifInstruction == null || !ifInstruction.FalseInst.MatchNop())
  297. return false;
  298. // the block ends in a return statement preceeded by an IfInstruction
  299. // take a look at the nested block and check if that might be a condition block
  300. if (last.MatchReturn(out _) && ifInstruction.TrueInst is Block nestedConditionBlock)
  301. {
  302. if (nestedConditionBlock.Instructions.Count < 2)
  303. return false;
  304. last = nestedConditionBlock.Instructions.Last();
  305. ifInstruction = nestedConditionBlock.Instructions.SecondToLastOrDefault() as IfInstruction;
  306. if (ifInstruction == null || !ifInstruction.FalseInst.MatchNop())
  307. return false;
  308. unwrapCondtionBlock = true;
  309. conditionBlock = nestedConditionBlock;
  310. }
  311. // if the last instruction is a branch
  312. // we assume the branch instructions need to be swapped.
  313. if (last.MatchBranch(loop.EntryPoint))
  314. swapBranches = true;
  315. else if (last.MatchLeave(loop))
  316. swapBranches = false;
  317. else
  318. return false;
  319. // match the IfInstruction
  320. if (swapBranches)
  321. {
  322. if (!ifInstruction.TrueInst.MatchLeave(loop))
  323. return false;
  324. }
  325. else
  326. {
  327. if (!ifInstruction.TrueInst.MatchBranch(loop.EntryPoint))
  328. return false;
  329. }
  330. return true;
  331. }
  332. // early match before block containers have been constructed
  333. internal static bool MatchDoWhileConditionBlock(Block block, out Block target1, out Block target2)
  334. {
  335. target1 = target2 = null;
  336. if (block.Instructions.Count < 2)
  337. return false;
  338. var last = block.Instructions.Last();
  339. if (!(block.Instructions.SecondToLastOrDefault() is IfInstruction ifInstruction) || !ifInstruction.FalseInst.MatchNop())
  340. return false;
  341. return (ifInstruction.TrueInst.MatchBranch(out target1) || ifInstruction.TrueInst.MatchReturn(out var _)) &&
  342. (last.MatchBranch(out target2) || last.MatchReturn(out var _));
  343. }
  344. internal static Block GetIncrementBlock(BlockContainer loop, Block whileLoopBody) =>
  345. loop.Blocks.SingleOrDefault(b => b != whileLoopBody
  346. && b.Instructions.Last().MatchBranch(loop.EntryPoint)
  347. && b.Instructions.SkipLast(1).All(IsSimpleStatement));
  348. internal static bool MatchIncrementBlock(Block block, out Block loopHead) =>
  349. block.Instructions.Last().MatchBranch(out loopHead)
  350. && block.Instructions.SkipLast(1).All(IsSimpleStatement);
  351. bool MatchForLoop(BlockContainer loop, IfInstruction whileCondition, Block whileLoopBody)
  352. {
  353. // for loops have exactly two incoming edges at the entry point.
  354. if (loop.EntryPoint.IncomingEdgeCount != 2)
  355. return false;
  356. // try to find an increment block:
  357. // consists of simple statements only.
  358. var incrementBlock = GetIncrementBlock(loop, whileLoopBody);
  359. if (incrementBlock != null)
  360. {
  361. // we found a possible increment block, just make sure, that there are at least three blocks:
  362. // - condition block
  363. // - loop body
  364. // - increment block
  365. if (incrementBlock.Instructions.Count <= 1 || loop.Blocks.Count < 3)
  366. return false;
  367. context.Step("Transform to for loop: " + loop.EntryPoint.Label, loop);
  368. // move the block to the end of the loop:
  369. loop.Blocks.MoveElementToEnd(incrementBlock);
  370. loop.Kind = ContainerKind.For;
  371. }
  372. else
  373. {
  374. // we need to move the increment statements into its own block:
  375. // last must be a branch entry-point
  376. var last = whileLoopBody.Instructions.LastOrDefault();
  377. var secondToLast = whileLoopBody.Instructions.SecondToLastOrDefault();
  378. if (last == null || secondToLast == null)
  379. return false;
  380. if (!last.MatchBranch(loop.EntryPoint))
  381. return false;
  382. // we only deal with 'numeric' increments
  383. if (!MatchIncrement(secondToLast, out var incrementVariable))
  384. return false;
  385. // the increment variable must be local/stack variable
  386. if (incrementVariable.Kind == VariableKind.Parameter)
  387. return false;
  388. // split conditions:
  389. var conditions = new List<ILInstruction>();
  390. SplitConditions(whileCondition.Condition, conditions);
  391. IfInstruction forCondition = null;
  392. int numberOfConditions = 0;
  393. foreach (var condition in conditions)
  394. {
  395. // the increment variable must be used in the condition
  396. if (!condition.Descendants.Any(inst => inst.MatchLdLoc(incrementVariable)))
  397. break;
  398. // condition should not contain an assignment
  399. if (condition.Descendants.Any(IsAssignment))
  400. break;
  401. if (forCondition == null)
  402. {
  403. forCondition = new IfInstruction(condition, whileCondition.TrueInst, whileCondition.FalseInst);
  404. }
  405. else
  406. {
  407. forCondition.Condition = IfInstruction.LogicAnd(forCondition.Condition, condition);
  408. }
  409. numberOfConditions++;
  410. }
  411. if (numberOfConditions == 0)
  412. return false;
  413. context.Step("Transform to for loop: " + loop.EntryPoint.Label, loop);
  414. // split condition block:
  415. whileCondition.ReplaceWith(forCondition);
  416. ExpressionTransforms.RunOnSingleStatement(forCondition, context);
  417. for (int i = conditions.Count - 1; i >= numberOfConditions; i--)
  418. {
  419. IfInstruction inst;
  420. whileLoopBody.Instructions.Insert(0, inst = new IfInstruction(Comp.LogicNot(conditions[i]), new Leave(loop)));
  421. ExpressionTransforms.RunOnSingleStatement(inst, context);
  422. }
  423. // create a new increment block and add it at the end:
  424. int secondToLastIndex = secondToLast.ChildIndex;
  425. var newIncremenBlock = new Block();
  426. loop.Blocks.Add(newIncremenBlock);
  427. // move the increment instruction:
  428. newIncremenBlock.Instructions.Add(secondToLast);
  429. newIncremenBlock.Instructions.Add(last);
  430. newIncremenBlock.AddILRange(secondToLast);
  431. whileLoopBody.Instructions.RemoveRange(secondToLastIndex, 2);
  432. whileLoopBody.Instructions.Add(new Branch(newIncremenBlock));
  433. // complete transform.
  434. loop.Kind = ContainerKind.For;
  435. }
  436. return true;
  437. }
  438. bool IsAssignment(ILInstruction inst)
  439. {
  440. if (inst is StLoc)
  441. return true;
  442. if (inst is CompoundAssignmentInstruction)
  443. return true;
  444. return false;
  445. }
  446. /// <summary>
  447. /// Returns true if the instruction is stloc v(add(ldloc v, arg))
  448. /// or compound.assign(ldloca v, arg)
  449. /// </summary>
  450. public static bool MatchIncrement(ILInstruction inst, out ILVariable variable)
  451. {
  452. if (inst.MatchStLoc(out variable, out var value))
  453. {
  454. if (value.MatchBinaryNumericInstruction(BinaryNumericOperator.Add, out var left, out var right))
  455. {
  456. return left.MatchLdLoc(variable);
  457. }
  458. }
  459. else if (inst is CompoundAssignmentInstruction cai)
  460. {
  461. return cai.TargetKind == CompoundTargetKind.Address && cai.Target.MatchLdLoca(out variable);
  462. }
  463. return false;
  464. }
  465. /// <summary>
  466. /// Gets whether the statement is 'simple' (usable as for loop iterator):
  467. /// Currently we only accept calls and assignments.
  468. /// </summary>
  469. static bool IsSimpleStatement(ILInstruction inst)
  470. {
  471. switch (inst.OpCode)
  472. {
  473. case OpCode.Call:
  474. case OpCode.CallVirt:
  475. case OpCode.NewObj:
  476. case OpCode.StLoc:
  477. case OpCode.StObj:
  478. case OpCode.NumericCompoundAssign:
  479. case OpCode.UserDefinedCompoundAssign:
  480. return true;
  481. default:
  482. return false;
  483. }
  484. }
  485. }
  486. }