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.

190 lines
7.4 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 ICSharpCode.Decompiler.IL.Transforms;
  19. using System;
  20. using System.Collections.Generic;
  21. using System.Linq;
  22. using System.Diagnostics;
  23. using ICSharpCode.Decompiler.Util;
  24. namespace ICSharpCode.Decompiler.IL.ControlFlow
  25. {
  26. /// <summary>
  27. /// C# switch statements are not necessarily compiled into
  28. /// IL switch instructions (e.g. when the integer values are non-contiguous).
  29. ///
  30. /// Detect sequences of conditional branches that all test a single integer value,
  31. /// and simplify them into a ILAst switch instruction (which like C# does not require contiguous values).
  32. /// </summary>
  33. class SwitchDetection : IILTransform
  34. {
  35. SwitchAnalysis analysis = new SwitchAnalysis();
  36. public void Run(ILFunction function, ILTransformContext context)
  37. {
  38. foreach (var container in function.Descendants.OfType<BlockContainer>()) {
  39. bool blockContainerNeedsCleanup = false;
  40. foreach (var block in container.Blocks) {
  41. context.CancellationToken.ThrowIfCancellationRequested();
  42. ProcessBlock(block, ref blockContainerNeedsCleanup);
  43. }
  44. if (blockContainerNeedsCleanup) {
  45. Debug.Assert(container.Blocks.All(b => b.Instructions.Count != 0 || b.IncomingEdgeCount == 0));
  46. container.Blocks.RemoveAll(b => b.Instructions.Count == 0);
  47. }
  48. }
  49. }
  50. void ProcessBlock(Block block, ref bool blockContainerNeedsCleanup)
  51. {
  52. bool analysisSuccess = analysis.AnalyzeBlock(block);
  53. KeyValuePair<LongSet, ILInstruction> defaultSection;
  54. if (analysisSuccess && UseCSharpSwitch(analysis, out defaultSection)) {
  55. // complex multi-block switch that can be combined into a single SwitchInstruction
  56. ILInstruction switchValue = new LdLoc(analysis.SwitchVariable);
  57. if (switchValue.ResultType == StackType.Unknown) {
  58. // switchValue must have a result type of either I4 or I8
  59. switchValue = new Conv(switchValue, PrimitiveType.I8, false, TypeSystem.Sign.Signed);
  60. }
  61. var sw = new SwitchInstruction(switchValue);
  62. foreach (var section in analysis.Sections) {
  63. sw.Sections.Add(new SwitchSection {
  64. Labels = section.Key,
  65. Body = section.Value
  66. });
  67. }
  68. if (block.Instructions.Last() is SwitchInstruction) {
  69. // we'll replace the switch
  70. } else {
  71. Debug.Assert(block.Instructions.SecondToLastOrDefault() is IfInstruction);
  72. // Remove branch/leave after if; it's getting moved into a section.
  73. block.Instructions.RemoveAt(block.Instructions.Count - 1);
  74. }
  75. block.Instructions[block.Instructions.Count - 1] = sw;
  76. // mark all inner blocks that were converted to the switch statement for deletion
  77. foreach (var innerBlock in analysis.InnerBlocks) {
  78. Debug.Assert(innerBlock.Parent == block.Parent);
  79. Debug.Assert(innerBlock != ((BlockContainer)block.Parent).EntryPoint);
  80. innerBlock.Instructions.Clear();
  81. }
  82. blockContainerNeedsCleanup = true;
  83. SortSwitchSections(sw);
  84. } else {
  85. // 2nd pass of SimplifySwitchInstruction (after duplicating return blocks),
  86. // (1st pass was in ControlFlowSimplification)
  87. SimplifySwitchInstruction(block);
  88. }
  89. }
  90. internal static void SimplifySwitchInstruction(Block block)
  91. {
  92. // due to our of of basic blocks at this point,
  93. // switch instructions can only appear as last insturction
  94. var sw = block.Instructions.LastOrDefault() as SwitchInstruction;
  95. if (sw == null)
  96. return;
  97. // ControlFlowSimplification runs early (before any other control flow transforms).
  98. // Any switch instructions will only have branch instructions in the sections.
  99. // Combine sections with identical branch target:
  100. var dict = new Dictionary<Block, SwitchSection>(); // branch target -> switch section
  101. sw.Sections.RemoveAll(
  102. section => {
  103. if (section.Body.MatchBranch(out Block target)) {
  104. if (dict.TryGetValue(target, out SwitchSection primarySection)) {
  105. primarySection.Labels = primarySection.Labels.UnionWith(section.Labels);
  106. primarySection.HasNullLabel |= section.HasNullLabel;
  107. return true; // remove this section
  108. } else {
  109. dict.Add(target, section);
  110. }
  111. }
  112. return false;
  113. });
  114. AdjustLabels(sw);
  115. SortSwitchSections(sw);
  116. }
  117. static void SortSwitchSections(SwitchInstruction sw)
  118. {
  119. sw.Sections.ReplaceList(sw.Sections.OrderBy(s => (s.Body as Branch)?.TargetILOffset).ThenBy(s => s.Labels.Values.FirstOrDefault()));
  120. }
  121. static void AdjustLabels(SwitchInstruction sw)
  122. {
  123. if (sw.Value is BinaryNumericInstruction bop && !bop.CheckForOverflow && bop.Right.MatchLdcI(out long val)) {
  124. // Move offset into labels:
  125. long offset;
  126. switch (bop.Operator) {
  127. case BinaryNumericOperator.Add:
  128. offset = unchecked(-val);
  129. break;
  130. case BinaryNumericOperator.Sub:
  131. offset = val;
  132. break;
  133. default: // unknown bop.Operator
  134. return;
  135. }
  136. sw.Value = bop.Left;
  137. foreach (var section in sw.Sections) {
  138. section.Labels = section.Labels.AddOffset(offset);
  139. }
  140. }
  141. }
  142. const ulong MaxValuesPerSection = 50;
  143. /// <summary>
  144. /// Tests whether we should prefer a switch statement over an if statement.
  145. /// </summary>
  146. static bool UseCSharpSwitch(SwitchAnalysis analysis, out KeyValuePair<LongSet, ILInstruction> defaultSection)
  147. {
  148. if (!analysis.InnerBlocks.Any()) {
  149. defaultSection = default(KeyValuePair<LongSet, ILInstruction>);
  150. return false;
  151. }
  152. defaultSection = analysis.Sections.FirstOrDefault(s => s.Key.Count() > MaxValuesPerSection);
  153. if (defaultSection.Value == null) {
  154. // no default section found?
  155. // This should never happen, as we'd need 2^64/MaxValuesPerSection sections to hit this case...
  156. return false;
  157. }
  158. ulong valuePerSectionLimit = MaxValuesPerSection;
  159. if (!analysis.ContainsILSwitch) {
  160. // If there's no IL switch involved, limit the number of keys per section
  161. // much more drastically to avoid generating switches where an if condition
  162. // would be shorter.
  163. valuePerSectionLimit = Math.Min(
  164. valuePerSectionLimit,
  165. (ulong)analysis.InnerBlocks.Count);
  166. }
  167. var defaultSectionKey = defaultSection.Key;
  168. if (analysis.Sections.Any(s => !s.Key.SetEquals(defaultSectionKey)
  169. && s.Key.Count() > valuePerSectionLimit)) {
  170. // Only the default section is allowed to have tons of keys.
  171. // C# doesn't support "case 1 to 100000000", and we don't want to generate
  172. // gigabytes of case labels.
  173. return false;
  174. }
  175. return true;
  176. }
  177. }
  178. }