123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Reflection;
- using System.Threading;
- using Mono.Cecil;
- using Mono.Cecil.Cil;
- using Unity.CompilationPipeline.Common.Diagnostics;
- using Unity.CompilationPipeline.Common.ILPostProcessing;
-
- namespace Unity.Jobs.CodeGen
- {
- // Jobs ILPP entry point
- internal partial class JobsILPostProcessor : ILPostProcessor
- {
- AssemblyDefinition AssemblyDefinition;
- List<DiagnosticMessage> DiagnosticMessages = new List<DiagnosticMessage>();
- public HashSet<string> Defines { get; private set; }
-
- public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
- {
- bool madeAnyChange = false;
- Defines = new HashSet<string>(compiledAssembly.Defines);
-
- try
- {
- AssemblyDefinition = AssemblyDefinitionFor(compiledAssembly);
- }
- catch (BadImageFormatException)
- {
- return new ILPostProcessResult(null, DiagnosticMessages);
- }
-
- try
- {
- // This only works because the PostProcessorAssemblyResolver is explicitly loading
- // transitive dependencies (and then some) and so if we can't find a references to
- // Unity.Jobs (via EarlyInitHelpers) in there than we are confident the assembly doesn't need processing
- var earlyInitHelpers = AssemblyDefinition.MainModule.ImportReference(typeof(EarlyInitHelpers)).CheckedResolve();
- }
- catch (ResolutionException)
- {
- return new ILPostProcessResult(null, DiagnosticMessages);
- }
-
- madeAnyChange = PostProcessImpl();
-
- // Hack to remove circular references
- var selfName = AssemblyDefinition.Name.FullName;
- foreach (var referenceName in AssemblyDefinition.MainModule.AssemblyReferences)
- {
- if (referenceName.FullName == selfName)
- {
- AssemblyDefinition.MainModule.AssemblyReferences.Remove(referenceName);
- break;
- }
- }
-
- if (!madeAnyChange)
- return new ILPostProcessResult(null, DiagnosticMessages);
-
- bool hasError = false;
- foreach (var d in DiagnosticMessages)
- {
- if (d.DiagnosticType == DiagnosticType.Error)
- {
- hasError = true;
- break;
- }
- }
-
- if (hasError)
- return new ILPostProcessResult(null, DiagnosticMessages);
-
- var pe = new MemoryStream();
- var pdb = new MemoryStream();
- var writerParameters = new WriterParameters
- {
- SymbolWriterProvider = new PortablePdbWriterProvider(), SymbolStream = pdb, WriteSymbols = true
- };
-
- AssemblyDefinition.Write(pe, writerParameters);
- return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), DiagnosticMessages);
- }
-
- public override ILPostProcessor GetInstance()
- {
- return this;
- }
-
- public override bool WillProcess(ICompiledAssembly compiledAssembly)
- {
- if (compiledAssembly.Name.EndsWith("CodeGen.Tests", StringComparison.Ordinal))
- return false;
-
- if (compiledAssembly.InMemoryAssembly.PdbData == null || compiledAssembly.InMemoryAssembly.PeData == null)
- return false;
-
- return true;
- }
-
- // *******************************************************************************
- // ** NOTE
- // ** Everything below this is a copy of the same process used in EntitiesILPostProcessor and
- // ** should stay synced with it.
- // *******************************************************************************
-
- class PostProcessorAssemblyResolver : IAssemblyResolver
- {
- private readonly HashSet<string> _referenceDirectories;
- private Dictionary<string, HashSet<string>> _referenceToPathMap;
- Dictionary<string, AssemblyDefinition> _cache = new Dictionary<string, AssemblyDefinition>();
- private ICompiledAssembly _compiledAssembly;
- private AssemblyDefinition _selfAssembly;
-
- public PostProcessorAssemblyResolver(ICompiledAssembly compiledAssembly)
- {
- _compiledAssembly = compiledAssembly;
- _referenceToPathMap = new Dictionary<string, HashSet<string>>();
- _referenceDirectories = new HashSet<string>();
- foreach (var reference in compiledAssembly.References)
- {
- var assemblyName = Path.GetFileNameWithoutExtension(reference);
- if (!_referenceToPathMap.TryGetValue(assemblyName, out var fileList))
- {
- fileList = new HashSet<string>();
- _referenceToPathMap.Add(assemblyName, fileList);
- }
- fileList.Add(reference);
- _referenceDirectories.Add(Path.GetDirectoryName(reference));
- }
- }
-
- public void Dispose()
- {
- }
-
- public AssemblyDefinition Resolve(AssemblyNameReference name)
- {
- return Resolve(name, new ReaderParameters(ReadingMode.Deferred));
- }
-
- public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
- {
- {
- if (name.Name == _compiledAssembly.Name)
- return _selfAssembly;
-
- var fileName = FindFile(name);
- if (fileName == null)
- return null;
-
- var cacheKey = fileName;
-
- if (_cache.TryGetValue(cacheKey, out var result))
- return result;
-
- parameters.AssemblyResolver = this;
-
- var ms = MemoryStreamFor(fileName);
-
- var pdb = fileName + ".pdb";
- if (File.Exists(pdb))
- parameters.SymbolStream = MemoryStreamFor(pdb);
-
- var assemblyDefinition = AssemblyDefinition.ReadAssembly(ms, parameters);
- _cache.Add(cacheKey, assemblyDefinition);
- return assemblyDefinition;
- }
- }
-
- private string FindFile(AssemblyNameReference name)
- {
- if (_referenceToPathMap.TryGetValue(name.Name, out var paths))
- {
- if (paths.Count == 1)
- {
- var enumerator = paths.GetEnumerator();
- // enumerators begin before the first element
- enumerator.MoveNext();
- return enumerator.Current;
- }
-
- // If we have more than one assembly with the same name loaded we now need to figure out which one
- // is being requested based on the AssemblyNameReference
- foreach (var path in paths)
- {
- var onDiskAssemblyName = AssemblyName.GetAssemblyName(path);
- if (onDiskAssemblyName.FullName == name.FullName)
- return path;
- }
- 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)}");
- }
-
- // Unfortunately the current ICompiledAssembly API only provides direct references.
- // It is very much possible that a postprocessor ends up investigating a type in a directly
- // referenced assembly, that contains a field that is not in a directly referenced assembly.
- // if we don't do anything special for that situation, it will fail to resolve. We should fix this
- // in the ILPostProcessing api. As a workaround, we rely on the fact here that the indirect references
- // are always located next to direct references, so we search in all directories of direct references we
- // got passed, and if we find the file in there, we resolve to it.
- foreach (var parentDir in _referenceDirectories)
- {
- var candidate = Path.Combine(parentDir, name.Name + ".dll");
- if (File.Exists(candidate))
- {
- if (!_referenceToPathMap.TryGetValue(candidate, out var referencePaths))
- {
- referencePaths = new HashSet<string>();
- _referenceToPathMap.Add(candidate, referencePaths);
- }
- referencePaths.Add(candidate);
-
- return candidate;
- }
- }
-
- return null;
- }
-
- static MemoryStream MemoryStreamFor(string fileName)
- {
- return Retry(10, TimeSpan.FromSeconds(1), () => {
- byte[] byteArray;
- using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
- {
- byteArray = new byte[fs.Length];
- var readLength = fs.Read(byteArray, 0, (int)fs.Length);
- if (readLength != fs.Length)
- throw new InvalidOperationException("File read length is not full length of file.");
- }
-
- return new MemoryStream(byteArray);
- });
- }
-
- private static MemoryStream Retry(int retryCount, TimeSpan waitTime, Func<MemoryStream> func)
- {
- try
- {
- return func();
- }
- catch (IOException)
- {
- if (retryCount == 0)
- throw;
- Console.WriteLine($"Caught IO Exception, trying {retryCount} more times");
- Thread.Sleep(waitTime);
- return Retry(retryCount - 1, waitTime, func);
- }
- }
-
- public void AddAssemblyDefinitionBeingOperatedOn(AssemblyDefinition assemblyDefinition)
- {
- _selfAssembly = assemblyDefinition;
- }
- }
-
- internal static AssemblyDefinition AssemblyDefinitionFor(ICompiledAssembly compiledAssembly)
- {
- var resolver = new PostProcessorAssemblyResolver(compiledAssembly);
- var readerParameters = new ReaderParameters
- {
- SymbolStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PdbData),
- SymbolReaderProvider = new PortablePdbReaderProvider(),
- AssemblyResolver = resolver,
- ReflectionImporterProvider = new PostProcessorReflectionImporterProvider(),
- ReadingMode = ReadingMode.Immediate
- };
-
- var peStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PeData);
- var assemblyDefinition = AssemblyDefinition.ReadAssembly(peStream, readerParameters);
-
- //apparently, it will happen that when we ask to resolve a type that lives inside Unity.Jobs, and we
- //are also postprocessing Unity.Jobs, type resolving will fail, because we do not actually try to resolve
- //inside the assembly we are processing. Let's make sure we do that, so that we can use postprocessor features inside
- //unity.Jobs itself as well.
- resolver.AddAssemblyDefinitionBeingOperatedOn(assemblyDefinition);
-
- return assemblyDefinition;
- }
- }
-
- internal class PostProcessorReflectionImporterProvider : IReflectionImporterProvider
- {
- public IReflectionImporter GetReflectionImporter(ModuleDefinition module)
- {
- return new PostProcessorReflectionImporter(module);
- }
- }
-
- internal class PostProcessorReflectionImporter : DefaultReflectionImporter
- {
- private const string SystemPrivateCoreLib = "System.Private.CoreLib";
- private AssemblyNameReference _correctCorlib;
-
- public PostProcessorReflectionImporter(ModuleDefinition module) : base(module)
- {
- _correctCorlib = default;
- foreach (var a in module.AssemblyReferences)
- {
- if (a.Name == "mscorlib" || a.Name == "netstandard" || a.Name == SystemPrivateCoreLib)
- {
- _correctCorlib = a;
- break;
- }
- }
- }
-
- public override AssemblyNameReference ImportReference(AssemblyName reference)
- {
- if (_correctCorlib != null && reference.Name == SystemPrivateCoreLib)
- return _correctCorlib;
-
- return base.ImportReference(reference);
- }
- }
- }
|