Geen omschrijving
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.

BurstILPostProcessor.cs 8.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. using Mono.Cecil;
  7. using Mono.Cecil.Cil;
  8. using Unity.CompilationPipeline.Common.Diagnostics;
  9. using Unity.CompilationPipeline.Common.ILPostProcessing;
  10. // TODO: Once DOTS has the latest 2022.2 editor merged into their fork, this can be UNITY_2022_2_OR_NEWER!
  11. #if UNITY_2023_1_OR_NEWER
  12. using ILPostProcessorToUse = zzzUnity.Burst.CodeGen.ILPostProcessing;
  13. #else
  14. using ILPostProcessorToUse = zzzUnity.Burst.CodeGen.ILPostProcessingLegacy;
  15. #endif
  16. /// Deliberately named zzzUnity.Burst.CodeGen, as we need to ensure its last in the chain
  17. namespace zzzUnity.Burst.CodeGen
  18. {
  19. /// <summary>
  20. /// Postprocessor used to replace calls from C# to [BurstCompile] functions to direct calls to
  21. /// Burst native code functions without having to go through a C# delegate.
  22. /// </summary>
  23. internal class BurstILPostProcessor : ILPostProcessor
  24. {
  25. private sealed class CachedAssemblyResolver : AssemblyResolver
  26. {
  27. private Dictionary<string, AssemblyDefinition> _cache = new Dictionary<string, AssemblyDefinition>();
  28. private Dictionary<string, string> _knownLocations = new Dictionary<string, string>();
  29. public void RegisterKnownLocation(string path)
  30. {
  31. var k = Path.GetFileNameWithoutExtension(path);
  32. // If an assembly is referenced multiple times, resolve to the first one
  33. if (!_knownLocations.ContainsKey(k))
  34. {
  35. _knownLocations.Add(k, path);
  36. }
  37. }
  38. public override AssemblyDefinition Resolve(AssemblyNameReference name)
  39. {
  40. if (!_cache.TryGetValue(name.FullName, out var definition))
  41. {
  42. if (_knownLocations.TryGetValue(name.Name, out var path))
  43. {
  44. definition = LoadFromFile(path);
  45. }
  46. else
  47. {
  48. definition = base.Resolve(name);
  49. }
  50. _cache.Add(name.FullName, definition);
  51. }
  52. return definition;
  53. }
  54. }
  55. public bool IsDebugging;
  56. public int DebuggingLevel;
  57. private void SetupDebugging()
  58. {
  59. // This can be setup to get more diagnostics
  60. var debuggingStr = Environment.GetEnvironmentVariable("UNITY_BURST_DEBUG");
  61. IsDebugging = debuggingStr != null;
  62. if (IsDebugging)
  63. {
  64. Log("[com.unity.burst] Extra debugging is turned on.");
  65. int debuggingLevel;
  66. int.TryParse(debuggingStr, out debuggingLevel);
  67. if (debuggingLevel <= 0) debuggingLevel = 1;
  68. DebuggingLevel = debuggingLevel;
  69. }
  70. }
  71. private static SequencePoint FindBestSequencePointFor(MethodDefinition method, Instruction instruction)
  72. {
  73. var sequencePoints = method.DebugInformation?.GetSequencePointMapping().Values.OrderBy(s => s.Offset).ToList();
  74. if (sequencePoints == null || !sequencePoints.Any())
  75. return null;
  76. for (int i = 0; i != sequencePoints.Count-1; i++)
  77. {
  78. if (sequencePoints[i].Offset < instruction.Offset &&
  79. sequencePoints[i + 1].Offset > instruction.Offset)
  80. return sequencePoints[i];
  81. }
  82. return sequencePoints.FirstOrDefault();
  83. }
  84. private static DiagnosticMessage MakeDiagnosticError(MethodDefinition method, Instruction errorLocation, string message)
  85. {
  86. var m = new DiagnosticMessage { DiagnosticType = DiagnosticType.Error };
  87. var sPoint = errorLocation != null ? FindBestSequencePointFor(method, errorLocation) : null;
  88. if (sPoint!=null)
  89. {
  90. m.Column = sPoint.StartColumn;
  91. m.Line = sPoint.StartLine;
  92. m.File = sPoint.Document.Url;
  93. }
  94. m.MessageData = message;
  95. return m;
  96. }
  97. public override unsafe ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
  98. {
  99. var diagnostics = new List<DiagnosticMessage>();
  100. if (!WillProcess(compiledAssembly))
  101. return new ILPostProcessResult(null, diagnostics);
  102. bool wasModified = false;
  103. SetupDebugging();
  104. bool debugging = IsDebugging && DebuggingLevel >= 2;
  105. var inMemoryAssembly = compiledAssembly.InMemoryAssembly;
  106. var peData = inMemoryAssembly.PeData;
  107. var pdbData = inMemoryAssembly.PdbData;
  108. var loader = new CachedAssemblyResolver();
  109. var folders = new HashSet<string>();
  110. var isForEditor = compiledAssembly.Defines?.Contains("UNITY_EDITOR") ?? false;
  111. foreach (var reference in compiledAssembly.References)
  112. {
  113. loader.RegisterKnownLocation(reference);
  114. folders.Add(Path.Combine(Environment.CurrentDirectory, Path.GetDirectoryName(reference)));
  115. }
  116. var folderList = folders.OrderBy(x => x).ToList();
  117. foreach (var folder in folderList)
  118. {
  119. loader.AddSearchDirectory(folder);
  120. }
  121. var clock = Stopwatch.StartNew();
  122. if (debugging)
  123. {
  124. Log($"Start processing assembly {compiledAssembly.Name}, IsForEditor: {isForEditor}, Folders: {string.Join("\n", folderList)}");
  125. }
  126. var ilPostProcessing = new ILPostProcessorToUse(loader, isForEditor,
  127. (m,i,s) => { diagnostics.Add(MakeDiagnosticError(m, i, s)); },
  128. IsDebugging ? Log : (LogDelegate)null, DebuggingLevel);
  129. var functionPointerProcessing = new FunctionPointerInvokeTransform(loader,
  130. (m,i,s) => { diagnostics.Add(MakeDiagnosticError(m, i, s)); },
  131. IsDebugging ? Log : (LogDelegate)null, DebuggingLevel);
  132. try
  133. {
  134. // For IL Post Processing, use the builtin symbol reader provider
  135. var assemblyDefinition = loader.LoadFromStream(new MemoryStream(peData), new MemoryStream(pdbData), new PortablePdbReaderProvider() );
  136. wasModified |= ilPostProcessing.Run(assemblyDefinition);
  137. wasModified |= functionPointerProcessing.Run(assemblyDefinition);
  138. if (wasModified)
  139. {
  140. var peStream = new MemoryStream();
  141. var pdbStream = new MemoryStream();
  142. var writeParameters = new WriterParameters
  143. {
  144. SymbolWriterProvider = new PortablePdbWriterProvider(),
  145. WriteSymbols = true,
  146. SymbolStream = pdbStream
  147. };
  148. assemblyDefinition.Write(peStream, writeParameters);
  149. peStream.Flush();
  150. pdbStream.Flush();
  151. peData = peStream.ToArray();
  152. pdbData = pdbStream.ToArray();
  153. }
  154. }
  155. catch (Exception ex)
  156. {
  157. throw new InvalidOperationException($"Internal compiler error for Burst ILPostProcessor on {compiledAssembly.Name}. Exception: {ex}");
  158. }
  159. if (debugging)
  160. {
  161. Log($"End processing assembly {compiledAssembly.Name} in {clock.Elapsed.TotalMilliseconds}ms.");
  162. }
  163. if (wasModified && !diagnostics.Any(d => d.DiagnosticType == DiagnosticType.Error))
  164. {
  165. return new ILPostProcessResult(new InMemoryAssembly(peData, pdbData), diagnostics);
  166. }
  167. return new ILPostProcessResult(null, diagnostics);
  168. }
  169. private static void Log(string message)
  170. {
  171. Console.WriteLine($"{nameof(BurstILPostProcessor)}: {message}");
  172. }
  173. public override ILPostProcessor GetInstance()
  174. {
  175. return this;
  176. }
  177. public override bool WillProcess(ICompiledAssembly compiledAssembly)
  178. {
  179. return compiledAssembly.References.Any(f => Path.GetFileName(f) == "Unity.Burst.dll");
  180. }
  181. }
  182. }