暫無描述
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.

JobReflectionDataPostProcessor.cs 16KB

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