A game about forced loneliness, made by TACStudios
at master 389 lines 16 kB view raw
1using System; 2using System.Collections.Generic; 3using Mono.Cecil; 4using Mono.Cecil.Cil; 5using Mono.Cecil.Rocks; 6using Unity.CompilationPipeline.Common.ILPostProcessing; 7using Unity.Jobs.LowLevel.Unsafe; 8using MethodAttributes = Mono.Cecil.MethodAttributes; 9using MethodBody = Mono.Cecil.Cil.MethodBody; 10using TypeAttributes = Mono.Cecil.TypeAttributes; 11 12namespace Unity.Jobs.CodeGen 13{ 14 internal partial class JobsILPostProcessor : ILPostProcessor 15 { 16 private static readonly string ProducerAttributeName = typeof(JobProducerTypeAttribute).FullName; 17 private static readonly string RegisterGenericJobTypeAttributeName = typeof(RegisterGenericJobTypeAttribute).FullName; 18 19 public static MethodReference AttributeConstructorReferenceFor(Type attributeType, ModuleDefinition module) 20 { 21 return module.ImportReference(attributeType.GetConstructor(Array.Empty<Type>())); 22 } 23 24 private TypeReference LaunderTypeRef(TypeReference r_) 25 { 26 ModuleDefinition mod = AssemblyDefinition.MainModule; 27 28 TypeDefinition def = r_.Resolve(); 29 30 TypeReference result; 31 32 if (r_ is GenericInstanceType git) 33 { 34 var gt = new GenericInstanceType(LaunderTypeRef(def)); 35 36 foreach (var gp in git.GenericParameters) 37 { 38 gt.GenericParameters.Add(gp); 39 } 40 41 foreach (var ga in git.GenericArguments) 42 { 43 gt.GenericArguments.Add(LaunderTypeRef(ga)); 44 } 45 46 result = gt; 47 48 } 49 else 50 { 51 result = new TypeReference(def.Namespace, def.Name, def.Module, def.Scope, def.IsValueType); 52 53 if (def.DeclaringType != null) 54 { 55 result.DeclaringType = LaunderTypeRef(def.DeclaringType); 56 } 57 } 58 59 return mod.ImportReference(result); 60 } 61 62 // http://www.isthe.com/chongo/src/fnv/hash_64a.c 63 static ulong StableHash_FNV1A64(string text) 64 { 65 ulong result = 14695981039346656037; 66 foreach (var c in text) 67 { 68 result = 1099511628211 * (result ^ (byte)(c & 255)); 69 result = 1099511628211 * (result ^ (byte)(c >> 8)); 70 } 71 return result; 72 } 73 74 bool PostProcessImpl() 75 { 76 bool anythingChanged = false; 77 78 var asmDef = AssemblyDefinition; 79 var funcDef = new MethodDefinition("CreateJobReflectionData", 80 MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.HideBySig, 81 asmDef.MainModule.ImportReference(typeof(void))); 82 83 // This must use a stable hash code function (do not using string.GetHashCode) 84 var autoClassName = $"__JobReflectionRegistrationOutput__{StableHash_FNV1A64(asmDef.FullName)}"; 85 86 funcDef.Body.InitLocals = false; 87 88 var classDef = new TypeDefinition("", autoClassName, TypeAttributes.Class, asmDef.MainModule.ImportReference(typeof(object))); 89 classDef.IsBeforeFieldInit = false; 90 classDef.CustomAttributes.Add(new CustomAttribute(AttributeConstructorReferenceFor(typeof(DOTSCompilerGeneratedAttribute), asmDef.MainModule))); 91 classDef.Methods.Add(funcDef); 92 93 var body = funcDef.Body; 94 var processor = body.GetILProcessor(); 95 96 // Setup instructions used for try/catch wrapping all earlyinit calls 97 // for this assembly's job types 98 var workStartOp = processor.Create(OpCodes.Nop); 99 var workDoneOp = Instruction.Create(OpCodes.Nop); 100 var handler = Instruction.Create(OpCodes.Nop); 101 var landingPad = Instruction.Create(OpCodes.Nop); 102 103 processor.Append(workStartOp); 104 105 var genericJobs = new List<TypeReference>(); 106 var visited = new HashSet<string>(); 107 108 foreach (var attr in asmDef.CustomAttributes) 109 { 110 if (attr.AttributeType.FullName != RegisterGenericJobTypeAttributeName) 111 continue; 112 113 var typeRef = (TypeReference)attr.ConstructorArguments[0].Value; 114 var openType = typeRef.Resolve(); 115 116 if (!typeRef.IsGenericInstance || !openType.IsValueType) 117 { 118 DiagnosticMessages.Add(UserError.DC3001(openType)); 119 continue; 120 } 121 122 genericJobs.Add(typeRef); 123 visited.Add(typeRef.FullName); 124 } 125 126 CollectGenericTypeInstances(AssemblyDefinition, genericJobs, visited); 127 128 foreach (var t in asmDef.MainModule.Types) 129 { 130 anythingChanged |= VisitJobStructs(t, processor, body); 131 } 132 133 foreach (var t in genericJobs) 134 { 135 anythingChanged |= VisitJobStructs(t, processor, body); 136 } 137 138 // Now that we have generated all reflection info 139 // finish wrapping the ops in a try catch now 140 var lastWorkOp = processor.Body.Instructions[processor.Body.Instructions.Count-1]; 141 processor.Append(handler); 142 143 var earlyInitHelpersDef = asmDef.MainModule.ImportReference(typeof(EarlyInitHelpers)).Resolve(); 144 MethodDefinition jobReflectionDataCreationFailedDef = null; 145 foreach (var method in earlyInitHelpersDef.Methods) 146 { 147 if (method.Name == nameof(EarlyInitHelpers.JobReflectionDataCreationFailed)) 148 { 149 jobReflectionDataCreationFailedDef = method; 150 break; 151 } 152 } 153 154 var errorHandler = asmDef.MainModule.ImportReference(jobReflectionDataCreationFailedDef); 155 processor.Append(Instruction.Create(OpCodes.Call, errorHandler)); 156 processor.Append(landingPad); 157 158 var leaveSuccess = Instruction.Create(OpCodes.Leave, landingPad); 159 var leaveFail = Instruction.Create(OpCodes.Leave, landingPad); 160 processor.InsertAfter(lastWorkOp, leaveSuccess); 161 processor.InsertBefore(landingPad, leaveFail); 162 163 var exc = new ExceptionHandler(ExceptionHandlerType.Catch); 164 exc.TryStart = workStartOp; 165 exc.TryEnd = leaveSuccess.Next; 166 exc.HandlerStart = handler; 167 exc.HandlerEnd = leaveFail.Next; 168 exc.CatchType = asmDef.MainModule.ImportReference(typeof(Exception)); 169 body.ExceptionHandlers.Add(exc); 170 171 processor.Emit(OpCodes.Ret); 172 173 if (anythingChanged) 174 { 175 var ctorFuncDef = new MethodDefinition("EarlyInit", MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.HideBySig, asmDef.MainModule.ImportReference(typeof(void))); 176 177 if (!Defines.Contains("UNITY_EDITOR")) 178 { 179 // Needs to run automatically in the player, but we need to 180 // exclude this attribute when building for the editor, or 181 // it will re-run the registration for every enter play mode. 182 var loadTypeEnumType = asmDef.MainModule.ImportReference(typeof(UnityEngine.RuntimeInitializeLoadType)); 183 var attributeCtor = asmDef.MainModule.ImportReference(typeof(UnityEngine.RuntimeInitializeOnLoadMethodAttribute).GetConstructor(new[] { typeof(UnityEngine.RuntimeInitializeLoadType) })); 184 var attribute = new CustomAttribute(attributeCtor); 185 attribute.ConstructorArguments.Add(new CustomAttributeArgument(loadTypeEnumType, UnityEngine.RuntimeInitializeLoadType.AfterAssembliesLoaded)); 186 ctorFuncDef.CustomAttributes.Add(attribute); 187 } 188 else 189 { 190 // Needs to run automatically in the editor. 191 var attributeCtor2 = asmDef.MainModule.ImportReference(typeof(UnityEditor.InitializeOnLoadMethodAttribute).GetConstructor(Type.EmptyTypes)); 192 ctorFuncDef.CustomAttributes.Add(new CustomAttribute(attributeCtor2)); 193 } 194 195 ctorFuncDef.Body.InitLocals = false; 196 197 var p = ctorFuncDef.Body.GetILProcessor(); 198 199 p.Emit(OpCodes.Call, funcDef); 200 p.Emit(OpCodes.Ret); 201 202 classDef.Methods.Add(ctorFuncDef); 203 204 asmDef.MainModule.Types.Add(classDef); 205 } 206 207 return anythingChanged; 208 } 209 210 private bool VisitJobStructInterfaces(TypeReference jobTypeRef, TypeDefinition jobType, TypeDefinition currentType, ILProcessor processor, MethodBody body) 211 { 212 bool didAnything = false; 213 214 if (currentType.HasInterfaces && jobType.IsValueType) 215 { 216 foreach (var iface in currentType.Interfaces) 217 { 218 var idef = iface.InterfaceType.CheckedResolve(); 219 220 foreach (var attr in idef.CustomAttributes) 221 { 222 if (attr.AttributeType.FullName == ProducerAttributeName) 223 { 224 var producerRef = (TypeReference)attr.ConstructorArguments[0].Value; 225 var launderedType = LaunderTypeRef(jobTypeRef); 226 didAnything |= GenerateCalls(producerRef, launderedType, body, processor); 227 } 228 229 if (currentType.IsInterface) 230 { 231 // Generic jobs need to be either reference in fully closed form, or registered explicitly with an attribute. 232 if (iface.InterfaceType.GenericParameters.Count == 0) 233 didAnything |= VisitJobStructInterfaces(jobTypeRef, jobType, idef, processor, body); 234 } 235 } 236 } 237 } 238 239 foreach (var nestedType in currentType.NestedTypes) 240 { 241 didAnything |= VisitJobStructs(nestedType, processor, body); 242 } 243 244 return didAnything; 245 } 246 247 private bool VisitJobStructs(TypeReference t, ILProcessor processor, MethodBody body) 248 { 249 if (t.GenericParameters.Count > 0) 250 { 251 // Generic jobs need to be either reference in fully closed form, or registered explicitly with an attribute. 252 return false; 253 } 254 255 var rt = t.CheckedResolve(); 256 257 return VisitJobStructInterfaces(t, rt, rt, processor, body); 258 } 259 260 private bool GenerateCalls(TypeReference producerRef, TypeReference jobStructType, MethodBody body, ILProcessor processor) 261 { 262 try 263 { 264 var carrierType = producerRef.CheckedResolve(); 265 MethodDefinition methodToCall = null; 266 while (carrierType != null) 267 { 268 methodToCall = null; 269 foreach (var method in carrierType.GetMethods()) 270 { 271 if(method.IsStatic && method.IsPublic && method.Parameters.Count == 0 && method.Name == "EarlyJobInit") 272 { 273 methodToCall = method; 274 break; 275 } 276 } 277 278 if (methodToCall != null) 279 break; 280 281 carrierType = carrierType.DeclaringType; 282 } 283 284 // Legacy jobs lazy initialize. 285 if (methodToCall == null) 286 return false; 287 288 var asm = AssemblyDefinition.MainModule; 289 var mref = asm.ImportReference(asm.ImportReference(methodToCall).MakeGenericInstanceMethod(jobStructType)); 290 processor.Append(Instruction.Create(OpCodes.Call, mref)); 291 292 return true; 293 } 294 catch (Exception ex) 295 { 296 DiagnosticMessages.Add(InternalCompilerError.DCICE300(producerRef, jobStructType, ex)); 297 } 298 299 return false; 300 } 301 302 private static void CollectGenericTypeInstances(AssemblyDefinition assembly, List<TypeReference> types, HashSet<string> visited) 303 { 304 // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 305 // WARNING: THIS CODE HAS TO BE MAINTAINED IN SYNC WITH BurstReflection.cs in Unity.Burst package 306 // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 307 308 // From: https://gist.github.com/xoofx/710aaf86e0e8c81649d1261b1ef9590e 309 if (assembly == null) throw new ArgumentNullException(nameof(assembly)); 310 const int mdMaxCount = 1 << 24; 311 foreach (var module in assembly.Modules) 312 { 313 for (int i = 1; i < mdMaxCount; i++) 314 { 315 // Token base id for TypeSpec 316 const int mdTypeSpec = 0x1B000000; 317 var token = module.LookupToken(mdTypeSpec | i); 318 if (token is GenericInstanceType type) 319 { 320 if (type.IsGenericInstance && !type.ContainsGenericParameter) 321 { 322 CollectGenericTypeInstances(type, types, visited); 323 } 324 } else if (token == null) break; 325 } 326 327 for (int i = 1; i < mdMaxCount; i++) 328 { 329 // Token base id for MethodSpec 330 const int mdMethodSpec = 0x2B000000; 331 var token = module.LookupToken(mdMethodSpec | i); 332 if (token is GenericInstanceMethod method) 333 { 334 foreach (var argType in method.GenericArguments) 335 { 336 if (argType.IsGenericInstance && !argType.ContainsGenericParameter) 337 { 338 CollectGenericTypeInstances(argType, types, visited); 339 } 340 } 341 } 342 else if (token == null) break; 343 } 344 345 for (int i = 1; i < mdMaxCount; i++) 346 { 347 // Token base id for Field 348 const int mdField = 0x04000000; 349 var token = module.LookupToken(mdField | i); 350 if (token is FieldReference field) 351 { 352 var fieldType = field.FieldType; 353 if (fieldType.IsGenericInstance && !fieldType.ContainsGenericParameter) 354 { 355 CollectGenericTypeInstances(fieldType, types, visited); 356 } 357 } 358 else if (token == null) break; 359 } 360 } 361 } 362 363 private static void CollectGenericTypeInstances(TypeReference type, List<TypeReference> types, HashSet<string> visited) 364 { 365 if (type.IsPrimitive) return; 366 if (!visited.Add(type.FullName)) return; 367 368 // Add only concrete types 369 if (type.IsGenericInstance && !type.ContainsGenericParameter) 370 { 371 types.Add(type); 372 } 373 374 // Collect recursively generic type arguments 375 var genericInstanceType = type as GenericInstanceType; 376 if (genericInstanceType != null) 377 { 378 foreach (var genericTypeArgument in genericInstanceType.GenericArguments) 379 { 380 if (!genericTypeArgument.IsPrimitive) 381 { 382 CollectGenericTypeInstances(genericTypeArgument, types, visited); 383 } 384 } 385 } 386 } 387 } 388} 389