jitdiff.cs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text.RegularExpressions;
  4. using System.IO;
  5. using System.Linq;
  6. namespace JitDiffTools
  7. {
  8. class Program
  9. {
  10. static void Main (string [] args)
  11. {
  12. if (args?.Length != 2)
  13. {
  14. Console.WriteLine ("usage:\n\tjitdiff folder1 folder2");
  15. return;
  16. }
  17. string [] filesBefore = Directory.GetFiles (args [0], "*.dasm");
  18. string [] filesAfter = Directory.GetFiles (args [1], "*.dasm");
  19. var pairs = new List<Tuple<string, string>> ();
  20. foreach (string fileBefore in filesBefore)
  21. {
  22. string fileName = Path.GetFileName (fileBefore);
  23. string fileAfter = filesAfter.FirstOrDefault (f =>
  24. Path.GetFileName (f).Equals (fileName, StringComparison.InvariantCultureIgnoreCase));
  25. if (fileAfter != null)
  26. pairs.Add (new Tuple<string, string> (fileBefore, fileAfter));
  27. }
  28. long totalFileDiff = 0;
  29. Console.WriteLine ();
  30. foreach (var pair in pairs)
  31. {
  32. long sizeBefore = new FileInfo (pair.Item1).Length;
  33. long sizeAfter = new FileInfo (pair.Item2).Length;
  34. long diff = sizeAfter - sizeBefore;
  35. totalFileDiff += diff;
  36. if (diff != 0)
  37. Console.WriteLine ($"Total diff for {Path.GetFileName (pair.Item1)}: {diff} bytes");
  38. }
  39. if (totalFileDiff != 0)
  40. Console.WriteLine ($"Total diff for all files: {totalFileDiff} bytes");
  41. Console.WriteLine ("\n=====================\n= Per-method diffs (may take a while):\n=====================\n");
  42. foreach (var pair in pairs)
  43. {
  44. PrintDiffs (pair.Item1, pair.Item2);
  45. }
  46. Console.WriteLine ("Done.");
  47. }
  48. static void PrintDiffs (string fileBefore, string fileAfter)
  49. {
  50. List<DiffItem> diff = GetDiffs (fileBefore, fileAfter);
  51. int totalRegression = 0, totalImprovement = 0;
  52. int methodRegressed = 0, methodImproved = 0;
  53. foreach (var diffItem in diff.OrderByDescending (d => d.DiffPercentage))
  54. {
  55. if (diffItem.HasChanges)
  56. {
  57. Console.WriteLine (diffItem);
  58. if (diffItem.Diff > 0)
  59. {
  60. totalRegression += diffItem.Diff;
  61. methodRegressed++;
  62. }
  63. else
  64. {
  65. totalImprovement += diffItem.Diff;
  66. methodImproved++;
  67. }
  68. }
  69. }
  70. if (methodRegressed == 0 && methodImproved == 0)
  71. return;
  72. Console.WriteLine ("\n");
  73. Console.WriteLine (Path.GetFileNameWithoutExtension (fileBefore));
  74. Console.WriteLine ($"Methods \"regressed\": {methodRegressed}");
  75. Console.WriteLine ($"Methods \"improved\": {methodImproved}");
  76. Console.WriteLine ($"Total regression: {totalRegression} lines, Total improvement: {totalImprovement} lines.");
  77. Console.WriteLine ("\n");
  78. }
  79. static List<DiffItem> GetDiffs (string file1, string file2)
  80. {
  81. List<FunctionInfo> file1Functions = ParseFunctions (file1);
  82. List<FunctionInfo> file2Functions = ParseFunctions (file2);
  83. var diffItems = new List<DiffItem> (); // diffs
  84. foreach (FunctionInfo file1Function in file1Functions)
  85. {
  86. // SingleOrDefault to make sure functions are unique
  87. FunctionInfo file2Function = file2Functions.FirstOrDefault (f => f.Name == file1Function.Name);
  88. diffItems.Add (new DiffItem (file1Function, file2Function)); // file2Function can be null here - means function was removed in file2
  89. }
  90. foreach (FunctionInfo file2Function in file2Functions)
  91. {
  92. // SingleOrDefault to make sure functions are unique
  93. FunctionInfo file1Function = file1Functions.FirstOrDefault (f => f.Name == file2Function.Name);
  94. if (file1Function == null)
  95. diffItems.Add (new DiffItem (null, file2Function)); // function was added in file2
  96. }
  97. return diffItems;
  98. }
  99. static bool TryParseFunctionName (string str, out string name)
  100. {
  101. // depends on objdump, let's use the whole line as a name if it ends with `:`
  102. if (str.EndsWith (':'))
  103. {
  104. // Possible formats:
  105. // 1) func_name:
  106. // 2) p_%var%_func_name:
  107. // 3) %var% <func_name>:
  108. // 4) %var% <p_%var%_func_name>:
  109. name = Regex.Replace (str, @"\b([a-f0-9]+)\b", m => "");
  110. name = Regex.Replace (name, @"(p_\d+_)?", m => "");
  111. return true;
  112. }
  113. name = null;
  114. return false;
  115. }
  116. static List<FunctionInfo> ParseFunctions (string file)
  117. {
  118. string [] lines = File.ReadAllLines (file)
  119. .Select (l => l.Trim (' ', '\t', '\r', '\n'))
  120. .Where (l => !string.IsNullOrEmpty (l))
  121. .ToArray ();
  122. var result = new List<FunctionInfo> ();
  123. FunctionInfo current = null;
  124. foreach (string line in lines)
  125. {
  126. if (TryParseFunctionName (line, out string name))
  127. {
  128. current = new FunctionInfo (name);
  129. result.Add (current);
  130. }
  131. current?.Body.Add (line);
  132. }
  133. return result;
  134. }
  135. }
  136. public class FunctionInfo
  137. {
  138. public FunctionInfo (string name)
  139. {
  140. if (string.IsNullOrWhiteSpace (name))
  141. throw new ArgumentException ("Function name should not be empty", nameof (name));
  142. Name = name;
  143. }
  144. public string Name { get; }
  145. public List<string> Body { get; set; } = new List<string>();
  146. public override string ToString () => $"{Name} (lines:{Body?.Count})";
  147. }
  148. public class DiffItem
  149. {
  150. public DiffItem (FunctionInfo before, FunctionInfo after)
  151. {
  152. if (before == null && after == null)
  153. throw new ArgumentException ("Both Before and After can not be null at the same time");
  154. if (before != null && after != null && before.Name != after.Name)
  155. throw new ArgumentException ("After.Name != Before.Name");
  156. Before = before;
  157. After = after;
  158. Name = before != null ? before.Name : after.Name;
  159. }
  160. public string Name { get; }
  161. public FunctionInfo Before { get; }
  162. public FunctionInfo After { get; }
  163. public int Diff => CalculateBytes (After) - CalculateBytes (Before);
  164. static int CalculateBytes (FunctionInfo info)
  165. {
  166. // TODO: calculate bytes
  167. return info?.Body?.Count ?? 0;
  168. }
  169. public bool HasChanges => Diff != 0;
  170. public double DiffPercentage
  171. {
  172. get
  173. {
  174. int b = Before?.Body?.Count ?? 0;
  175. int a = (After?.Body?.Count ?? 0) * 100;
  176. if (a == 0 && b == 0)
  177. return 0;
  178. if (a > 0 && b == 0)
  179. return -100;
  180. return -(100 - a / b);
  181. }
  182. }
  183. public override string ToString () => $"Diff for {Name}: {Diff} lines ({DiffPercentage:F1}%)";
  184. }
  185. }