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