No Description
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.

JobsILPostProcessor.cs 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Reflection;
  5. using System.Threading;
  6. using Mono.Cecil;
  7. using Mono.Cecil.Cil;
  8. using Unity.CompilationPipeline.Common.Diagnostics;
  9. using Unity.CompilationPipeline.Common.ILPostProcessing;
  10. namespace Unity.Jobs.CodeGen
  11. {
  12. // Jobs ILPP entry point
  13. internal partial class JobsILPostProcessor : ILPostProcessor
  14. {
  15. AssemblyDefinition AssemblyDefinition;
  16. List<DiagnosticMessage> DiagnosticMessages = new List<DiagnosticMessage>();
  17. public HashSet<string> Defines { get; private set; }
  18. public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
  19. {
  20. bool madeAnyChange = false;
  21. Defines = new HashSet<string>(compiledAssembly.Defines);
  22. try
  23. {
  24. AssemblyDefinition = AssemblyDefinitionFor(compiledAssembly);
  25. }
  26. catch (BadImageFormatException)
  27. {
  28. return new ILPostProcessResult(null, DiagnosticMessages);
  29. }
  30. try
  31. {
  32. // This only works because the PostProcessorAssemblyResolver is explicitly loading
  33. // transitive dependencies (and then some) and so if we can't find a references to
  34. // Unity.Jobs (via EarlyInitHelpers) in there than we are confident the assembly doesn't need processing
  35. var earlyInitHelpers = AssemblyDefinition.MainModule.ImportReference(typeof(EarlyInitHelpers)).CheckedResolve();
  36. }
  37. catch (ResolutionException)
  38. {
  39. return new ILPostProcessResult(null, DiagnosticMessages);
  40. }
  41. madeAnyChange = PostProcessImpl();
  42. // Hack to remove circular references
  43. var selfName = AssemblyDefinition.Name.FullName;
  44. foreach (var referenceName in AssemblyDefinition.MainModule.AssemblyReferences)
  45. {
  46. if (referenceName.FullName == selfName)
  47. {
  48. AssemblyDefinition.MainModule.AssemblyReferences.Remove(referenceName);
  49. break;
  50. }
  51. }
  52. if (!madeAnyChange)
  53. return new ILPostProcessResult(null, DiagnosticMessages);
  54. bool hasError = false;
  55. foreach (var d in DiagnosticMessages)
  56. {
  57. if (d.DiagnosticType == DiagnosticType.Error)
  58. {
  59. hasError = true;
  60. break;
  61. }
  62. }
  63. if (hasError)
  64. return new ILPostProcessResult(null, DiagnosticMessages);
  65. var pe = new MemoryStream();
  66. var pdb = new MemoryStream();
  67. var writerParameters = new WriterParameters
  68. {
  69. SymbolWriterProvider = new PortablePdbWriterProvider(), SymbolStream = pdb, WriteSymbols = true
  70. };
  71. AssemblyDefinition.Write(pe, writerParameters);
  72. return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), DiagnosticMessages);
  73. }
  74. public override ILPostProcessor GetInstance()
  75. {
  76. return this;
  77. }
  78. public override bool WillProcess(ICompiledAssembly compiledAssembly)
  79. {
  80. if (compiledAssembly.Name.EndsWith("CodeGen.Tests", StringComparison.Ordinal))
  81. return false;
  82. if (compiledAssembly.InMemoryAssembly.PdbData == null || compiledAssembly.InMemoryAssembly.PeData == null)
  83. return false;
  84. return true;
  85. }
  86. // *******************************************************************************
  87. // ** NOTE
  88. // ** Everything below this is a copy of the same process used in EntitiesILPostProcessor and
  89. // ** should stay synced with it.
  90. // *******************************************************************************
  91. class PostProcessorAssemblyResolver : IAssemblyResolver
  92. {
  93. private readonly HashSet<string> _referenceDirectories;
  94. private Dictionary<string, HashSet<string>> _referenceToPathMap;
  95. Dictionary<string, AssemblyDefinition> _cache = new Dictionary<string, AssemblyDefinition>();
  96. private ICompiledAssembly _compiledAssembly;
  97. private AssemblyDefinition _selfAssembly;
  98. public PostProcessorAssemblyResolver(ICompiledAssembly compiledAssembly)
  99. {
  100. _compiledAssembly = compiledAssembly;
  101. _referenceToPathMap = new Dictionary<string, HashSet<string>>();
  102. _referenceDirectories = new HashSet<string>();
  103. foreach (var reference in compiledAssembly.References)
  104. {
  105. var assemblyName = Path.GetFileNameWithoutExtension(reference);
  106. if (!_referenceToPathMap.TryGetValue(assemblyName, out var fileList))
  107. {
  108. fileList = new HashSet<string>();
  109. _referenceToPathMap.Add(assemblyName, fileList);
  110. }
  111. fileList.Add(reference);
  112. _referenceDirectories.Add(Path.GetDirectoryName(reference));
  113. }
  114. }
  115. public void Dispose()
  116. {
  117. }
  118. public AssemblyDefinition Resolve(AssemblyNameReference name)
  119. {
  120. return Resolve(name, new ReaderParameters(ReadingMode.Deferred));
  121. }
  122. public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
  123. {
  124. {
  125. if (name.Name == _compiledAssembly.Name)
  126. return _selfAssembly;
  127. var fileName = FindFile(name);
  128. if (fileName == null)
  129. return null;
  130. var cacheKey = fileName;
  131. if (_cache.TryGetValue(cacheKey, out var result))
  132. return result;
  133. parameters.AssemblyResolver = this;
  134. var ms = MemoryStreamFor(fileName);
  135. var pdb = fileName + ".pdb";
  136. if (File.Exists(pdb))
  137. parameters.SymbolStream = MemoryStreamFor(pdb);
  138. var assemblyDefinition = AssemblyDefinition.ReadAssembly(ms, parameters);
  139. _cache.Add(cacheKey, assemblyDefinition);
  140. return assemblyDefinition;
  141. }
  142. }
  143. private string FindFile(AssemblyNameReference name)
  144. {
  145. if (_referenceToPathMap.TryGetValue(name.Name, out var paths))
  146. {
  147. if (paths.Count == 1)
  148. {
  149. var enumerator = paths.GetEnumerator();
  150. // enumerators begin before the first element
  151. enumerator.MoveNext();
  152. return enumerator.Current;
  153. }
  154. // If we have more than one assembly with the same name loaded we now need to figure out which one
  155. // is being requested based on the AssemblyNameReference
  156. foreach (var path in paths)
  157. {
  158. var onDiskAssemblyName = AssemblyName.GetAssemblyName(path);
  159. if (onDiskAssemblyName.FullName == name.FullName)
  160. return path;
  161. }
  162. throw new ArgumentException($"Tried to resolve a reference in assembly '{name.FullName}' however the assembly could not be found. Known references which did not match: \n{string.Join("\n",paths)}");
  163. }
  164. // Unfortunately the current ICompiledAssembly API only provides direct references.
  165. // It is very much possible that a postprocessor ends up investigating a type in a directly
  166. // referenced assembly, that contains a field that is not in a directly referenced assembly.
  167. // if we don't do anything special for that situation, it will fail to resolve. We should fix this
  168. // in the ILPostProcessing api. As a workaround, we rely on the fact here that the indirect references
  169. // are always located next to direct references, so we search in all directories of direct references we
  170. // got passed, and if we find the file in there, we resolve to it.
  171. foreach (var parentDir in _referenceDirectories)
  172. {
  173. var candidate = Path.Combine(parentDir, name.Name + ".dll");
  174. if (File.Exists(candidate))
  175. {
  176. if (!_referenceToPathMap.TryGetValue(candidate, out var referencePaths))
  177. {
  178. referencePaths = new HashSet<string>();
  179. _referenceToPathMap.Add(candidate, referencePaths);
  180. }
  181. referencePaths.Add(candidate);
  182. return candidate;
  183. }
  184. }
  185. return null;
  186. }
  187. static MemoryStream MemoryStreamFor(string fileName)
  188. {
  189. return Retry(10, TimeSpan.FromSeconds(1), () => {
  190. byte[] byteArray;
  191. using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
  192. {
  193. byteArray = new byte[fs.Length];
  194. var readLength = fs.Read(byteArray, 0, (int)fs.Length);
  195. if (readLength != fs.Length)
  196. throw new InvalidOperationException("File read length is not full length of file.");
  197. }
  198. return new MemoryStream(byteArray);
  199. });
  200. }
  201. private static MemoryStream Retry(int retryCount, TimeSpan waitTime, Func<MemoryStream> func)
  202. {
  203. try
  204. {
  205. return func();
  206. }
  207. catch (IOException)
  208. {
  209. if (retryCount == 0)
  210. throw;
  211. Console.WriteLine($"Caught IO Exception, trying {retryCount} more times");
  212. Thread.Sleep(waitTime);
  213. return Retry(retryCount - 1, waitTime, func);
  214. }
  215. }
  216. public void AddAssemblyDefinitionBeingOperatedOn(AssemblyDefinition assemblyDefinition)
  217. {
  218. _selfAssembly = assemblyDefinition;
  219. }
  220. }
  221. internal static AssemblyDefinition AssemblyDefinitionFor(ICompiledAssembly compiledAssembly)
  222. {
  223. var resolver = new PostProcessorAssemblyResolver(compiledAssembly);
  224. var readerParameters = new ReaderParameters
  225. {
  226. SymbolStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PdbData),
  227. SymbolReaderProvider = new PortablePdbReaderProvider(),
  228. AssemblyResolver = resolver,
  229. ReflectionImporterProvider = new PostProcessorReflectionImporterProvider(),
  230. ReadingMode = ReadingMode.Immediate
  231. };
  232. var peStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PeData);
  233. var assemblyDefinition = AssemblyDefinition.ReadAssembly(peStream, readerParameters);
  234. //apparently, it will happen that when we ask to resolve a type that lives inside Unity.Jobs, and we
  235. //are also postprocessing Unity.Jobs, type resolving will fail, because we do not actually try to resolve
  236. //inside the assembly we are processing. Let's make sure we do that, so that we can use postprocessor features inside
  237. //unity.Jobs itself as well.
  238. resolver.AddAssemblyDefinitionBeingOperatedOn(assemblyDefinition);
  239. return assemblyDefinition;
  240. }
  241. }
  242. internal class PostProcessorReflectionImporterProvider : IReflectionImporterProvider
  243. {
  244. public IReflectionImporter GetReflectionImporter(ModuleDefinition module)
  245. {
  246. return new PostProcessorReflectionImporter(module);
  247. }
  248. }
  249. internal class PostProcessorReflectionImporter : DefaultReflectionImporter
  250. {
  251. private const string SystemPrivateCoreLib = "System.Private.CoreLib";
  252. private AssemblyNameReference _correctCorlib;
  253. public PostProcessorReflectionImporter(ModuleDefinition module) : base(module)
  254. {
  255. _correctCorlib = default;
  256. foreach (var a in module.AssemblyReferences)
  257. {
  258. if (a.Name == "mscorlib" || a.Name == "netstandard" || a.Name == SystemPrivateCoreLib)
  259. {
  260. _correctCorlib = a;
  261. break;
  262. }
  263. }
  264. }
  265. public override AssemblyNameReference ImportReference(AssemblyName reference)
  266. {
  267. if (_correctCorlib != null && reference.Name == SystemPrivateCoreLib)
  268. return _correctCorlib;
  269. return base.ImportReference(reference);
  270. }
  271. }
  272. }