390 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			390 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | ||
|  | using System.Collections.Generic; | ||
|  | using Mono.Cecil; | ||
|  | using Mono.Cecil.Cil; | ||
|  | using Mono.Cecil.Rocks; | ||
|  | using Unity.CompilationPipeline.Common.ILPostProcessing; | ||
|  | using Unity.Jobs.LowLevel.Unsafe; | ||
|  | using MethodAttributes = Mono.Cecil.MethodAttributes; | ||
|  | using MethodBody = Mono.Cecil.Cil.MethodBody; | ||
|  | using TypeAttributes = Mono.Cecil.TypeAttributes; | ||
|  | 
 | ||
|  | namespace Unity.Jobs.CodeGen | ||
|  | { | ||
|  |     internal partial class JobsILPostProcessor : ILPostProcessor | ||
|  |     { | ||
|  |         private static readonly string ProducerAttributeName = typeof(JobProducerTypeAttribute).FullName; | ||
|  |         private static readonly string RegisterGenericJobTypeAttributeName = typeof(RegisterGenericJobTypeAttribute).FullName; | ||
|  | 
 | ||
|  |         public static MethodReference AttributeConstructorReferenceFor(Type attributeType, ModuleDefinition module) | ||
|  |         { | ||
|  |             return module.ImportReference(attributeType.GetConstructor(Array.Empty<Type>())); | ||
|  |         } | ||
|  | 
 | ||
|  |         private TypeReference LaunderTypeRef(TypeReference r_) | ||
|  |         { | ||
|  |             ModuleDefinition mod = AssemblyDefinition.MainModule; | ||
|  | 
 | ||
|  |             TypeDefinition def = r_.Resolve(); | ||
|  | 
 | ||
|  |             TypeReference result; | ||
|  | 
 | ||
|  |             if (r_ is GenericInstanceType git) | ||
|  |             { | ||
|  |                 var gt = new GenericInstanceType(LaunderTypeRef(def)); | ||
|  | 
 | ||
|  |                 foreach (var gp in git.GenericParameters) | ||
|  |                 { | ||
|  |                     gt.GenericParameters.Add(gp); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 foreach (var ga in git.GenericArguments) | ||
|  |                 { | ||
|  |                     gt.GenericArguments.Add(LaunderTypeRef(ga)); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 result = gt; | ||
|  | 
 | ||
|  |             } | ||
|  |             else | ||
|  |             { | ||
|  |                 result = new TypeReference(def.Namespace, def.Name, def.Module, def.Scope, def.IsValueType); | ||
|  | 
 | ||
|  |                 if (def.DeclaringType != null) | ||
|  |                 { | ||
|  |                     result.DeclaringType = LaunderTypeRef(def.DeclaringType); | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             return mod.ImportReference(result); | ||
|  |         } | ||
|  | 
 | ||
|  |         // http://www.isthe.com/chongo/src/fnv/hash_64a.c | ||
|  |         static ulong StableHash_FNV1A64(string text) | ||
|  |         { | ||
|  |             ulong result = 14695981039346656037; | ||
|  |             foreach (var c in text) | ||
|  |             { | ||
|  |                 result = 1099511628211 * (result ^ (byte)(c & 255)); | ||
|  |                 result = 1099511628211 * (result ^ (byte)(c >> 8)); | ||
|  |             } | ||
|  |             return result; | ||
|  |         } | ||
|  | 
 | ||
|  |         bool PostProcessImpl() | ||
|  |         { | ||
|  |             bool anythingChanged = false; | ||
|  | 
 | ||
|  |             var asmDef = AssemblyDefinition; | ||
|  |             var funcDef = new MethodDefinition("CreateJobReflectionData", | ||
|  |                 MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.HideBySig, | ||
|  |                 asmDef.MainModule.ImportReference(typeof(void))); | ||
|  | 
 | ||
|  |             // This must use a stable hash code function (do not using string.GetHashCode) | ||
|  |             var autoClassName = $"__JobReflectionRegistrationOutput__{StableHash_FNV1A64(asmDef.FullName)}"; | ||
|  | 
 | ||
|  |             funcDef.Body.InitLocals = false; | ||
|  | 
 | ||
|  |             var classDef = new TypeDefinition("", autoClassName, TypeAttributes.Class, asmDef.MainModule.ImportReference(typeof(object))); | ||
|  |             classDef.IsBeforeFieldInit = false; | ||
|  |             classDef.CustomAttributes.Add(new CustomAttribute(AttributeConstructorReferenceFor(typeof(DOTSCompilerGeneratedAttribute), asmDef.MainModule))); | ||
|  |             classDef.Methods.Add(funcDef); | ||
|  | 
 | ||
|  |             var body = funcDef.Body; | ||
|  |             var processor = body.GetILProcessor(); | ||
|  | 
 | ||
|  |             // Setup instructions used for try/catch wrapping all earlyinit calls | ||
|  |             // for this assembly's job types | ||
|  |             var workStartOp = processor.Create(OpCodes.Nop); | ||
|  |             var workDoneOp = Instruction.Create(OpCodes.Nop); | ||
|  |             var handler = Instruction.Create(OpCodes.Nop); | ||
|  |             var landingPad = Instruction.Create(OpCodes.Nop); | ||
|  | 
 | ||
|  |             processor.Append(workStartOp); | ||
|  | 
 | ||
|  |             var genericJobs = new List<TypeReference>(); | ||
|  |             var visited = new HashSet<string>(); | ||
|  | 
 | ||
|  |             foreach (var attr in asmDef.CustomAttributes) | ||
|  |             { | ||
|  |                 if (attr.AttributeType.FullName != RegisterGenericJobTypeAttributeName) | ||
|  |                     continue; | ||
|  | 
 | ||
|  |                 var typeRef = (TypeReference)attr.ConstructorArguments[0].Value; | ||
|  |                 var openType = typeRef.Resolve(); | ||
|  | 
 | ||
|  |                 if (!typeRef.IsGenericInstance || !openType.IsValueType) | ||
|  |                 { | ||
|  |                     DiagnosticMessages.Add(UserError.DC3001(openType)); | ||
|  |                     continue; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 genericJobs.Add(typeRef); | ||
|  |                 visited.Add(typeRef.FullName); | ||
|  |             } | ||
|  | 
 | ||
|  |             CollectGenericTypeInstances(AssemblyDefinition, genericJobs, visited); | ||
|  | 
 | ||
|  |             foreach (var t in asmDef.MainModule.Types) | ||
|  |             { | ||
|  |                 anythingChanged |= VisitJobStructs(t, processor, body); | ||
|  |             } | ||
|  | 
 | ||
|  |             foreach (var t in genericJobs) | ||
|  |             { | ||
|  |                 anythingChanged |= VisitJobStructs(t, processor, body); | ||
|  |             } | ||
|  | 
 | ||
|  |             // Now that we have generated all reflection info | ||
|  |             // finish wrapping the ops in a try catch now | ||
|  |             var lastWorkOp = processor.Body.Instructions[processor.Body.Instructions.Count-1]; | ||
|  |             processor.Append(handler); | ||
|  | 
 | ||
|  |             var earlyInitHelpersDef = asmDef.MainModule.ImportReference(typeof(EarlyInitHelpers)).Resolve(); | ||
|  |             MethodDefinition jobReflectionDataCreationFailedDef = null; | ||
|  |             foreach (var method in earlyInitHelpersDef.Methods) | ||
|  |             { | ||
|  |                 if (method.Name == nameof(EarlyInitHelpers.JobReflectionDataCreationFailed)) | ||
|  |                 { | ||
|  |                     jobReflectionDataCreationFailedDef = method; | ||
|  |                     break; | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             var errorHandler = asmDef.MainModule.ImportReference(jobReflectionDataCreationFailedDef); | ||
|  |             processor.Append(Instruction.Create(OpCodes.Call, errorHandler)); | ||
|  |             processor.Append(landingPad); | ||
|  | 
 | ||
|  |             var leaveSuccess = Instruction.Create(OpCodes.Leave, landingPad); | ||
|  |             var leaveFail = Instruction.Create(OpCodes.Leave, landingPad); | ||
|  |             processor.InsertAfter(lastWorkOp, leaveSuccess); | ||
|  |             processor.InsertBefore(landingPad, leaveFail); | ||
|  | 
 | ||
|  |             var exc = new ExceptionHandler(ExceptionHandlerType.Catch); | ||
|  |             exc.TryStart = workStartOp; | ||
|  |             exc.TryEnd = leaveSuccess.Next; | ||
|  |             exc.HandlerStart = handler; | ||
|  |             exc.HandlerEnd = leaveFail.Next; | ||
|  |             exc.CatchType = asmDef.MainModule.ImportReference(typeof(Exception)); | ||
|  |             body.ExceptionHandlers.Add(exc); | ||
|  | 
 | ||
|  |             processor.Emit(OpCodes.Ret); | ||
|  | 
 | ||
|  |             if (anythingChanged) | ||
|  |             { | ||
|  |                 var ctorFuncDef = new MethodDefinition("EarlyInit", MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.HideBySig, asmDef.MainModule.ImportReference(typeof(void))); | ||
|  | 
 | ||
|  |                 if (!Defines.Contains("UNITY_EDITOR")) | ||
|  |                 { | ||
|  |                     // Needs to run automatically in the player, but we need to | ||
|  |                     // exclude this attribute when building for the editor, or | ||
|  |                     // it will re-run the registration for every enter play mode. | ||
|  |                     var loadTypeEnumType = asmDef.MainModule.ImportReference(typeof(UnityEngine.RuntimeInitializeLoadType)); | ||
|  |                     var attributeCtor = asmDef.MainModule.ImportReference(typeof(UnityEngine.RuntimeInitializeOnLoadMethodAttribute).GetConstructor(new[] { typeof(UnityEngine.RuntimeInitializeLoadType) })); | ||
|  |                     var attribute = new CustomAttribute(attributeCtor); | ||
|  |                     attribute.ConstructorArguments.Add(new CustomAttributeArgument(loadTypeEnumType, UnityEngine.RuntimeInitializeLoadType.AfterAssembliesLoaded)); | ||
|  |                     ctorFuncDef.CustomAttributes.Add(attribute); | ||
|  |                 } | ||
|  |                 else | ||
|  |                 { | ||
|  |                     // Needs to run automatically in the editor. | ||
|  |                     var attributeCtor2 = asmDef.MainModule.ImportReference(typeof(UnityEditor.InitializeOnLoadMethodAttribute).GetConstructor(Type.EmptyTypes)); | ||
|  |                     ctorFuncDef.CustomAttributes.Add(new CustomAttribute(attributeCtor2)); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 ctorFuncDef.Body.InitLocals = false; | ||
|  | 
 | ||
|  |                 var p = ctorFuncDef.Body.GetILProcessor(); | ||
|  | 
 | ||
|  |                 p.Emit(OpCodes.Call, funcDef); | ||
|  |                 p.Emit(OpCodes.Ret); | ||
|  | 
 | ||
|  |                 classDef.Methods.Add(ctorFuncDef); | ||
|  | 
 | ||
|  |                 asmDef.MainModule.Types.Add(classDef); | ||
|  |             } | ||
|  | 
 | ||
|  |             return anythingChanged; | ||
|  |         } | ||
|  | 
 | ||
|  |         private bool VisitJobStructInterfaces(TypeReference jobTypeRef, TypeDefinition jobType, TypeDefinition currentType, ILProcessor processor, MethodBody body) | ||
|  |         { | ||
|  |             bool didAnything = false; | ||
|  | 
 | ||
|  |             if (currentType.HasInterfaces && jobType.IsValueType) | ||
|  |             { | ||
|  |                 foreach (var iface in currentType.Interfaces) | ||
|  |                 { | ||
|  |                     var idef = iface.InterfaceType.CheckedResolve(); | ||
|  | 
 | ||
|  |                     foreach (var attr in idef.CustomAttributes) | ||
|  |                     { | ||
|  |                         if (attr.AttributeType.FullName == ProducerAttributeName) | ||
|  |                         { | ||
|  |                             var producerRef = (TypeReference)attr.ConstructorArguments[0].Value; | ||
|  |                             var launderedType = LaunderTypeRef(jobTypeRef); | ||
|  |                             didAnything |= GenerateCalls(producerRef, launderedType, body, processor); | ||
|  |                         } | ||
|  | 
 | ||
|  |                         if (currentType.IsInterface) | ||
|  |                         { | ||
|  |                             // Generic jobs need to be either reference in fully closed form, or registered explicitly with an attribute. | ||
|  |                             if (iface.InterfaceType.GenericParameters.Count == 0) | ||
|  |                                 didAnything |= VisitJobStructInterfaces(jobTypeRef, jobType, idef, processor, body); | ||
|  |                         } | ||
|  |                     } | ||
|  |                 } | ||
|  |             } | ||
|  | 
 | ||
|  |             foreach (var nestedType in currentType.NestedTypes) | ||
|  |             { | ||
|  |                 didAnything |= VisitJobStructs(nestedType, processor, body); | ||
|  |             } | ||
|  | 
 | ||
|  |             return didAnything; | ||
|  |         } | ||
|  | 
 | ||
|  |         private bool VisitJobStructs(TypeReference t, ILProcessor processor, MethodBody body) | ||
|  |         { | ||
|  |             if (t.GenericParameters.Count > 0) | ||
|  |             { | ||
|  |                 // Generic jobs need to be either reference in fully closed form, or registered explicitly with an attribute. | ||
|  |                 return false; | ||
|  |             } | ||
|  | 
 | ||
|  |             var rt = t.CheckedResolve(); | ||
|  | 
 | ||
|  |             return VisitJobStructInterfaces(t, rt, rt, processor, body); | ||
|  |         } | ||
|  | 
 | ||
|  |         private bool GenerateCalls(TypeReference producerRef, TypeReference jobStructType, MethodBody body, ILProcessor processor) | ||
|  |         { | ||
|  |             try | ||
|  |             { | ||
|  |                 var carrierType = producerRef.CheckedResolve(); | ||
|  |                 MethodDefinition methodToCall = null; | ||
|  |                 while (carrierType != null) | ||
|  |                 { | ||
|  |                     methodToCall = null; | ||
|  |                     foreach (var method in carrierType.GetMethods()) | ||
|  |                     { | ||
|  |                         if(method.IsStatic && method.IsPublic && method.Parameters.Count == 0 && method.Name == "EarlyJobInit") | ||
|  |                         { | ||
|  |                             methodToCall = method; | ||
|  |                             break; | ||
|  |                         } | ||
|  |                     } | ||
|  | 
 | ||
|  |                     if (methodToCall != null) | ||
|  |                         break; | ||
|  | 
 | ||
|  |                     carrierType = carrierType.DeclaringType; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 // Legacy jobs lazy initialize. | ||
|  |                 if (methodToCall == null) | ||
|  |                     return false; | ||
|  | 
 | ||
|  |                 var asm = AssemblyDefinition.MainModule; | ||
|  |                 var mref = asm.ImportReference(asm.ImportReference(methodToCall).MakeGenericInstanceMethod(jobStructType)); | ||
|  |                 processor.Append(Instruction.Create(OpCodes.Call, mref)); | ||
|  | 
 | ||
|  |                 return true; | ||
|  |             } | ||
|  |             catch (Exception ex) | ||
|  |             { | ||
|  |                 DiagnosticMessages.Add(InternalCompilerError.DCICE300(producerRef, jobStructType, ex)); | ||
|  |             } | ||
|  | 
 | ||
|  |             return false; | ||
|  |         } | ||
|  | 
 | ||
|  |         private static void CollectGenericTypeInstances(AssemblyDefinition assembly, List<TypeReference> types, HashSet<string> visited) | ||
|  |         { | ||
|  |             // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | ||
|  |             // WARNING: THIS CODE HAS TO BE MAINTAINED IN SYNC WITH BurstReflection.cs in Unity.Burst package | ||
|  |             // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | ||
|  | 
 | ||
|  |             // From: https://gist.github.com/xoofx/710aaf86e0e8c81649d1261b1ef9590e | ||
|  |             if (assembly == null) throw new ArgumentNullException(nameof(assembly)); | ||
|  |             const int mdMaxCount = 1 << 24; | ||
|  |             foreach (var module in assembly.Modules) | ||
|  |             { | ||
|  |                 for (int i = 1; i < mdMaxCount; i++) | ||
|  |                 { | ||
|  |                     // Token base id for TypeSpec | ||
|  |                     const int mdTypeSpec = 0x1B000000; | ||
|  |                     var token = module.LookupToken(mdTypeSpec | i); | ||
|  |                     if (token is GenericInstanceType type) | ||
|  |                     { | ||
|  |                         if (type.IsGenericInstance && !type.ContainsGenericParameter) | ||
|  |                         { | ||
|  |                             CollectGenericTypeInstances(type, types, visited); | ||
|  |                         } | ||
|  |                     } else if (token == null) break; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 for (int i = 1; i < mdMaxCount; i++) | ||
|  |                 { | ||
|  |                     // Token base id for MethodSpec | ||
|  |                     const int mdMethodSpec = 0x2B000000; | ||
|  |                     var token = module.LookupToken(mdMethodSpec | i); | ||
|  |                     if (token is GenericInstanceMethod method) | ||
|  |                     { | ||
|  |                         foreach (var argType in method.GenericArguments) | ||
|  |                         { | ||
|  |                             if (argType.IsGenericInstance && !argType.ContainsGenericParameter) | ||
|  |                             { | ||
|  |                                 CollectGenericTypeInstances(argType, types, visited); | ||
|  |                             } | ||
|  |                         } | ||
|  |                     } | ||
|  |                     else if (token == null) break; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 for (int i = 1; i < mdMaxCount; i++) | ||
|  |                 { | ||
|  |                     // Token base id for Field | ||
|  |                     const int mdField = 0x04000000; | ||
|  |                     var token = module.LookupToken(mdField | i); | ||
|  |                     if (token is FieldReference field) | ||
|  |                     { | ||
|  |                         var fieldType = field.FieldType; | ||
|  |                         if (fieldType.IsGenericInstance && !fieldType.ContainsGenericParameter) | ||
|  |                         { | ||
|  |                             CollectGenericTypeInstances(fieldType, types, visited); | ||
|  |                         } | ||
|  |                     } | ||
|  |                     else if (token == null) break; | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         private static void CollectGenericTypeInstances(TypeReference type, List<TypeReference> types, HashSet<string> visited) | ||
|  |         { | ||
|  |             if (type.IsPrimitive) return; | ||
|  |             if (!visited.Add(type.FullName)) return; | ||
|  | 
 | ||
|  |             // Add only concrete types | ||
|  |             if (type.IsGenericInstance && !type.ContainsGenericParameter) | ||
|  |             { | ||
|  |                 types.Add(type); | ||
|  |             } | ||
|  | 
 | ||
|  |             // Collect recursively generic type arguments | ||
|  |             var genericInstanceType = type as GenericInstanceType; | ||
|  |             if (genericInstanceType != null) | ||
|  |             { | ||
|  |                 foreach (var genericTypeArgument in genericInstanceType.GenericArguments) | ||
|  |                 { | ||
|  |                     if (!genericTypeArgument.IsPrimitive) | ||
|  |                     { | ||
|  |                         CollectGenericTypeInstances(genericTypeArgument, types, visited); | ||
|  |                     } | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  | } | ||
|  | 
 |