No Description
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.

BurstReflection.cs 38KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  1. #if UNITY_EDITOR
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Globalization;
  6. using System.Linq;
  7. using System.Reflection;
  8. using System.Runtime.CompilerServices;
  9. using Unity.Jobs.LowLevel.Unsafe;
  10. using UnityEditor;
  11. using UnityEditor.Compilation;
  12. using Debug = UnityEngine.Debug;
  13. [assembly: InternalsVisibleTo("Unity.Burst.Editor.Tests")]
  14. namespace Unity.Burst.Editor
  15. {
  16. using static BurstCompilerOptions;
  17. internal static class BurstReflection
  18. {
  19. // The TypeCache API was added in 2019.2. So there are two versions of FindExecuteMethods,
  20. // one that uses TypeCache and one that doesn't.
  21. #if UNITY_2019_2_OR_NEWER
  22. public static FindExecuteMethodsResult FindExecuteMethods(List<System.Reflection.Assembly> assemblyList, BurstReflectionAssemblyOptions options)
  23. {
  24. var methodsToCompile = new List<BurstCompileTarget>();
  25. var methodsToCompileSet = new HashSet<MethodInfo>();
  26. var logMessages = new List<LogMessage>();
  27. var interfaceToProducer = new Dictionary<Type, Type>();
  28. var assemblySet = new HashSet<System.Reflection.Assembly>(assemblyList);
  29. void AddTarget(BurstCompileTarget target)
  30. {
  31. // We will not try to record more than once a method in the methods to compile
  32. // This can happen if a job interface is inheriting from another job interface which are using in the end the same
  33. // job producer type
  34. if (!target.IsStaticMethod && !methodsToCompileSet.Add(target.Method))
  35. {
  36. return;
  37. }
  38. if (options.HasFlag(BurstReflectionAssemblyOptions.ExcludeTestAssemblies) &&
  39. target.JobType.Assembly.GetReferencedAssemblies().Any(x => IsNUnitDll(x.Name)))
  40. {
  41. return;
  42. }
  43. methodsToCompile.Add(target);
  44. }
  45. var staticMethodTypes = new HashSet<Type>();
  46. // -------------------------------------------
  47. // Find job structs using TypeCache.
  48. // -------------------------------------------
  49. var jobProducerImplementations = TypeCache.GetTypesWithAttribute<JobProducerTypeAttribute>();
  50. foreach (var jobProducerImplementation in jobProducerImplementations)
  51. {
  52. var attrs = jobProducerImplementation.GetCustomAttributes(typeof(JobProducerTypeAttribute), false);
  53. if (attrs.Length == 0)
  54. {
  55. continue;
  56. }
  57. staticMethodTypes.Add(jobProducerImplementation);
  58. var attr = (JobProducerTypeAttribute)attrs[0];
  59. interfaceToProducer.Add(jobProducerImplementation, attr.ProducerType);
  60. }
  61. foreach (var jobProducerImplementation in jobProducerImplementations)
  62. {
  63. if (!jobProducerImplementation.IsInterface)
  64. {
  65. continue;
  66. }
  67. var jobTypes = TypeCache.GetTypesDerivedFrom(jobProducerImplementation);
  68. foreach (var jobType in jobTypes)
  69. {
  70. if (jobType.IsGenericType || !jobType.IsValueType)
  71. {
  72. continue;
  73. }
  74. ScanJobType(jobType, interfaceToProducer, logMessages, AddTarget);
  75. }
  76. }
  77. // -------------------------------------------
  78. // Find static methods using TypeCache.
  79. // -------------------------------------------
  80. void AddStaticMethods(TypeCache.MethodCollection methods)
  81. {
  82. foreach (var method in methods)
  83. {
  84. if (HasBurstCompileAttribute(method.DeclaringType))
  85. {
  86. staticMethodTypes.Add(method.DeclaringType);
  87. // NOTE: Make sure that we don't use a value type generic definition (e.g `class Outer<T> { struct Inner { } }`)
  88. // We are only working on plain type or generic type instance!
  89. if (!method.DeclaringType.IsGenericTypeDefinition &&
  90. method.IsStatic &&
  91. !method.ContainsGenericParameters)
  92. {
  93. AddTarget(new BurstCompileTarget(method, method.DeclaringType, null, true));
  94. }
  95. }
  96. }
  97. }
  98. // Add [BurstCompile] static methods.
  99. AddStaticMethods(TypeCache.GetMethodsWithAttribute<BurstCompileAttribute>());
  100. // Add [TestCompiler] static methods.
  101. if (!options.HasFlag(BurstReflectionAssemblyOptions.ExcludeTestAssemblies))
  102. {
  103. var testCompilerAttributeType = Type.GetType("Burst.Compiler.IL.Tests.TestCompilerAttribute, Unity.Burst.Tests.UnitTests, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null");
  104. if (testCompilerAttributeType != null)
  105. {
  106. AddStaticMethods(TypeCache.GetMethodsWithAttribute(testCompilerAttributeType));
  107. }
  108. }
  109. // -------------------------------------------
  110. // Find job types and static methods based on
  111. // generic instances types. These will not be
  112. // found by the TypeCache scanning above.
  113. // -------------------------------------------
  114. FindExecuteMethodsForGenericInstances(
  115. assemblySet,
  116. staticMethodTypes,
  117. interfaceToProducer,
  118. AddTarget,
  119. logMessages);
  120. return new FindExecuteMethodsResult(methodsToCompile, logMessages);
  121. }
  122. private static void ScanJobType(
  123. Type jobType,
  124. Dictionary<Type, Type> interfaceToProducer,
  125. List<LogMessage> logMessages,
  126. Action<BurstCompileTarget> addTarget)
  127. {
  128. foreach (var interfaceType in jobType.GetInterfaces())
  129. {
  130. var genericLessInterface = interfaceType;
  131. if (interfaceType.IsGenericType)
  132. {
  133. genericLessInterface = interfaceType.GetGenericTypeDefinition();
  134. }
  135. if (interfaceToProducer.TryGetValue(genericLessInterface, out var foundProducer))
  136. {
  137. var genericParams = new List<Type> { jobType };
  138. if (interfaceType.IsGenericType)
  139. {
  140. genericParams.AddRange(interfaceType.GenericTypeArguments);
  141. }
  142. try
  143. {
  144. var executeType = foundProducer.MakeGenericType(genericParams.ToArray());
  145. var executeMethod = executeType.GetMethod("Execute", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
  146. if (executeMethod == null)
  147. {
  148. throw new InvalidOperationException($"Burst reflection error. The type `{executeType}` does not contain an `Execute` method");
  149. }
  150. addTarget(new BurstCompileTarget(executeMethod, jobType, interfaceType, false));
  151. }
  152. catch (Exception ex)
  153. {
  154. logMessages.Add(new LogMessage(ex));
  155. }
  156. }
  157. }
  158. }
  159. private static void FindExecuteMethodsForGenericInstances(
  160. HashSet<System.Reflection.Assembly> assemblyList,
  161. HashSet<Type> staticMethodTypes,
  162. Dictionary<Type, Type> interfaceToProducer,
  163. Action<BurstCompileTarget> addTarget,
  164. List<LogMessage> logMessages)
  165. {
  166. var valueTypes = new List<TypeToVisit>();
  167. //Debug.Log("Filtered Assembly List: " + string.Join(", ", assemblyList.Select(assembly => assembly.GetName().Name)));
  168. // Find all ways to execute job types (via producer attributes)
  169. var typesVisited = new HashSet<string>();
  170. var typesToVisit = new HashSet<string>();
  171. var allTypesAssembliesCollected = new HashSet<Type>();
  172. foreach (var assembly in assemblyList)
  173. {
  174. var types = new List<Type>();
  175. try
  176. {
  177. // Collect all generic type instances (excluding indirect instances)
  178. CollectGenericTypeInstances(
  179. assembly,
  180. x => assemblyList.Contains(x.Assembly),
  181. types,
  182. allTypesAssembliesCollected);
  183. }
  184. catch (Exception ex)
  185. {
  186. logMessages.Add(new LogMessage(LogType.Warning, "Unexpected exception while collecting types in assembly `" + assembly.FullName + "` Exception: " + ex));
  187. }
  188. for (var i = 0; i < types.Count; i++)
  189. {
  190. var t = types[i];
  191. if (typesToVisit.Add(t.AssemblyQualifiedName))
  192. {
  193. // Because the list of types returned by CollectGenericTypeInstances does not detect nested generic classes that are not
  194. // used explicitly, we need to create them if a declaring type is actually used
  195. // so for example if we have:
  196. // class MyClass<T> { class MyNestedClass { } }
  197. // class MyDerived : MyClass<int> { }
  198. // The CollectGenericTypeInstances will return typically the type MyClass<int>, but will not list MyClass<int>.MyNestedClass
  199. // So the following code is correcting this in order to fully query the full graph of generic instance types, including indirect types
  200. var nestedTypes = t.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic);
  201. foreach (var nestedType in nestedTypes)
  202. {
  203. if (t.IsGenericType && !t.IsGenericTypeDefinition)
  204. {
  205. var parentGenericTypeArguments = t.GetGenericArguments();
  206. // Only create nested types that are closed generic types (full generic instance types)
  207. // It happens if for example the parent class is `class MClass<T> { class MyNestedGeneric<T1> {} }`
  208. // In that case, MyNestedGeneric<T1> is opened in the context of MClass<int>, so we don't process them
  209. if (nestedType.GetGenericArguments().Length == parentGenericTypeArguments.Length)
  210. {
  211. try
  212. {
  213. var instanceNestedType = nestedType.MakeGenericType(parentGenericTypeArguments);
  214. types.Add(instanceNestedType);
  215. }
  216. catch (Exception ex)
  217. {
  218. var error = $"Unexpected Burst Inspector error. Invalid generic type instance. Trying to instantiate the generic type {nestedType.FullName} with the generic arguments <{string.Join(", ", parentGenericTypeArguments.Select(x => x.FullName))}> is not supported: {ex}";
  219. logMessages.Add(new LogMessage(LogType.Warning, error));
  220. }
  221. }
  222. }
  223. else
  224. {
  225. types.Add(nestedType);
  226. }
  227. }
  228. }
  229. }
  230. foreach (var t in types)
  231. {
  232. // If the type has been already visited, don't try to visit it
  233. if (!typesVisited.Add(t.AssemblyQualifiedName) || (t.IsGenericTypeDefinition && !t.IsInterface))
  234. {
  235. continue;
  236. }
  237. try
  238. {
  239. // collect methods with types having a [BurstCompile] attribute
  240. var staticMethodDeclaringType = t;
  241. if (t.IsGenericType)
  242. {
  243. staticMethodDeclaringType = t.GetGenericTypeDefinition();
  244. }
  245. bool visitStaticMethods = staticMethodTypes.Contains(staticMethodDeclaringType);
  246. bool isValueType = false;
  247. if (t.IsValueType)
  248. {
  249. // NOTE: Make sure that we don't use a value type generic definition (e.g `class Outer<T> { struct Inner { } }`)
  250. // We are only working on plain type or generic type instance!
  251. if (!t.IsGenericTypeDefinition)
  252. isValueType = true;
  253. }
  254. if (isValueType || visitStaticMethods)
  255. {
  256. valueTypes.Add(new TypeToVisit(t, visitStaticMethods));
  257. }
  258. }
  259. catch (Exception ex)
  260. {
  261. logMessages.Add(new LogMessage(LogType.Warning,
  262. "Unexpected exception while inspecting type `" + t +
  263. "` IsConstructedGenericType: " + t.IsConstructedGenericType +
  264. " IsGenericTypeDef: " + t.IsGenericTypeDefinition +
  265. " IsGenericParam: " + t.IsGenericParameter +
  266. " Exception: " + ex));
  267. }
  268. }
  269. }
  270. // Revisit all types to find things that are compilable using the above producers.
  271. foreach (var typePair in valueTypes)
  272. {
  273. var type = typePair.Type;
  274. // collect static [BurstCompile] methods
  275. if (typePair.CollectStaticMethods)
  276. {
  277. try
  278. {
  279. var methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
  280. foreach (var method in methods)
  281. {
  282. if (HasBurstCompileAttribute(method))
  283. {
  284. addTarget(new BurstCompileTarget(method, type, null, true));
  285. }
  286. }
  287. }
  288. catch (Exception ex)
  289. {
  290. logMessages.Add(new LogMessage(ex));
  291. }
  292. }
  293. // If the type is not a value type, we don't need to proceed with struct Jobs
  294. if (!type.IsValueType)
  295. {
  296. continue;
  297. }
  298. ScanJobType(type, interfaceToProducer, logMessages, addTarget);
  299. }
  300. }
  301. #else
  302. public static FindExecuteMethodsResult FindExecuteMethods(IEnumerable<System.Reflection.Assembly> assemblyList, BurstReflectionAssemblyOptions options)
  303. {
  304. var methodsToCompile = new List<BurstCompileTarget>();
  305. var methodsToCompileSet = new HashSet<MethodInfo>();
  306. var valueTypes = new List<TypeToVisit>();
  307. var interfaceToProducer = new Dictionary<Type, Type>();
  308. // This method can be called on a background thread, so we can't call Debug.Log etc.
  309. // Instead collect all the log messages and return them.
  310. var logMessages = new List<LogMessage>();
  311. //Debug.Log("Filtered Assembly List: " + string.Join(", ", assemblyList.Select(assembly => assembly.GetName().Name)));
  312. // Find all ways to execute job types (via producer attributes)
  313. var typesVisited = new HashSet<string>();
  314. var typesToVisit = new HashSet<string>();
  315. var allTypesAssembliesCollected = new HashSet<Type>();
  316. foreach (var assembly in assemblyList)
  317. {
  318. var types = new List<Type>();
  319. try
  320. {
  321. var typesFromAssembly = assembly.GetTypes();
  322. types.AddRange(typesFromAssembly);
  323. foreach(var typeInAssembly in typesFromAssembly)
  324. {
  325. allTypesAssembliesCollected.Add(typeInAssembly);
  326. }
  327. // Collect all generic type instances (excluding indirect instances)
  328. CollectGenericTypeInstances(assembly, x => true, types, allTypesAssembliesCollected);
  329. }
  330. catch (Exception ex)
  331. {
  332. logMessages.Add(new LogMessage(LogType.Warning, "Unexpected exception while collecting types in assembly `" + assembly.FullName + "` Exception: " + ex));
  333. }
  334. for (var i = 0; i < types.Count; i++)
  335. {
  336. var t = types[i];
  337. if (typesToVisit.Add(t.AssemblyQualifiedName))
  338. {
  339. // Because the list of types returned by CollectGenericTypeInstances does not detect nested generic classes that are not
  340. // used explicitly, we need to create them if a declaring type is actually used
  341. // so for example if we have:
  342. // class MyClass<T> { class MyNestedClass { } }
  343. // class MyDerived : MyClass<int> { }
  344. // The CollectGenericTypeInstances will return typically the type MyClass<int>, but will not list MyClass<int>.MyNestedClass
  345. // So the following code is correcting this in order to fully query the full graph of generic instance types, including indirect types
  346. var nestedTypes = t.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic);
  347. foreach (var nestedType in nestedTypes)
  348. {
  349. if (t.IsGenericType && !t.IsGenericTypeDefinition)
  350. {
  351. var parentGenericTypeArguments = t.GetGenericArguments();
  352. // Only create nested types that are closed generic types (full generic instance types)
  353. // It happens if for example the parent class is `class MClass<T> { class MyNestedGeneric<T1> {} }`
  354. // In that case, MyNestedGeneric<T1> is opened in the context of MClass<int>, so we don't process them
  355. if (nestedType.GetGenericArguments().Length == parentGenericTypeArguments.Length)
  356. {
  357. try
  358. {
  359. var instanceNestedType = nestedType.MakeGenericType(parentGenericTypeArguments);
  360. types.Add(instanceNestedType);
  361. }
  362. catch (Exception ex)
  363. {
  364. var error = $"Unexpected Burst Inspector error. Invalid generic type instance. Trying to instantiate the generic type {nestedType.FullName} with the generic arguments <{string.Join(", ", parentGenericTypeArguments.Select(x => x.FullName))}> is not supported: {ex}";
  365. logMessages.Add(new LogMessage(LogType.Warning, error));
  366. }
  367. }
  368. }
  369. else
  370. {
  371. types.Add(nestedType);
  372. }
  373. }
  374. }
  375. }
  376. foreach (var t in types)
  377. {
  378. // If the type has been already visited, don't try to visit it
  379. if (!typesVisited.Add(t.AssemblyQualifiedName) || (t.IsGenericTypeDefinition && !t.IsInterface))
  380. {
  381. continue;
  382. }
  383. try
  384. {
  385. // collect methods with types having a [BurstCompile] attribute
  386. bool visitStaticMethods = HasBurstCompileAttribute(t);
  387. bool isValueType = false;
  388. if (t.IsInterface)
  389. {
  390. object[] attrs = t.GetCustomAttributes(typeof(JobProducerTypeAttribute), false);
  391. if (attrs.Length == 0)
  392. continue;
  393. JobProducerTypeAttribute attr = (JobProducerTypeAttribute)attrs[0];
  394. interfaceToProducer.Add(t, attr.ProducerType);
  395. //Debug.Log($"{t} has producer {attr.ProducerType}");
  396. }
  397. else if (t.IsValueType)
  398. {
  399. // NOTE: Make sure that we don't use a value type generic definition (e.g `class Outer<T> { struct Inner { } }`)
  400. // We are only working on plain type or generic type instance!
  401. if (!t.IsGenericTypeDefinition)
  402. isValueType = true;
  403. }
  404. if (isValueType || visitStaticMethods)
  405. {
  406. valueTypes.Add(new TypeToVisit(t, visitStaticMethods));
  407. }
  408. }
  409. catch (Exception ex)
  410. {
  411. logMessages.Add(new LogMessage(LogType.Warning,
  412. "Unexpected exception while inspecting type `" + t +
  413. "` IsConstructedGenericType: " + t.IsConstructedGenericType +
  414. " IsGenericTypeDef: " + t.IsGenericTypeDefinition +
  415. " IsGenericParam: " + t.IsGenericParameter +
  416. " Exception: " + ex));
  417. }
  418. }
  419. }
  420. //Debug.Log($"Mapped {interfaceToProducer.Count} producers; {valueTypes.Count} value types");
  421. // Revisit all types to find things that are compilable using the above producers.
  422. foreach (var typePair in valueTypes)
  423. {
  424. var type = typePair.Type;
  425. // collect static [BurstCompile] methods
  426. if (typePair.CollectStaticMethods)
  427. {
  428. try
  429. {
  430. var methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
  431. foreach (var method in methods)
  432. {
  433. if (!method.ContainsGenericParameters && HasBurstCompileAttribute(method))
  434. {
  435. var target = new BurstCompileTarget(method, type, null, true);
  436. methodsToCompile.Add(target);
  437. }
  438. }
  439. }
  440. catch (Exception ex)
  441. {
  442. logMessages.Add(new LogMessage(ex));
  443. }
  444. }
  445. // If the type is not a value type, we don't need to proceed with struct Jobs
  446. if (!type.IsValueType)
  447. {
  448. continue;
  449. }
  450. // Otherwise try to find if we have an interface producer setup on the class
  451. foreach (var interfaceType in type.GetInterfaces())
  452. {
  453. var genericLessInterface = interfaceType;
  454. if (interfaceType.IsGenericType)
  455. {
  456. genericLessInterface = interfaceType.GetGenericTypeDefinition();
  457. }
  458. Type foundProducer;
  459. if (interfaceToProducer.TryGetValue(genericLessInterface, out foundProducer))
  460. {
  461. var genericParams = new List<Type> {type};
  462. if (interfaceType.IsGenericType)
  463. {
  464. genericParams.AddRange(interfaceType.GenericTypeArguments);
  465. }
  466. try
  467. {
  468. var executeType = foundProducer.MakeGenericType(genericParams.ToArray());
  469. var executeMethod = executeType.GetMethod("Execute", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
  470. if (executeMethod == null)
  471. {
  472. throw new InvalidOperationException($"Burst reflection error. The type `{executeType}` does not contain an `Execute` method");
  473. }
  474. // We will not try to record more than once a method in the methods to compile
  475. // This can happen if a job interface is inheriting from another job interface which are using in the end the same
  476. // job producer type
  477. if (methodsToCompileSet.Add(executeMethod))
  478. {
  479. var target = new BurstCompileTarget(executeMethod, type, interfaceType, false);
  480. methodsToCompile.Add(target);
  481. }
  482. }
  483. catch (Exception ex)
  484. {
  485. logMessages.Add(new LogMessage(ex));
  486. }
  487. }
  488. }
  489. }
  490. return new FindExecuteMethodsResult(methodsToCompile, logMessages);
  491. }
  492. #endif
  493. public sealed class FindExecuteMethodsResult
  494. {
  495. public readonly List<BurstCompileTarget> CompileTargets;
  496. public readonly List<LogMessage> LogMessages;
  497. public FindExecuteMethodsResult(List<BurstCompileTarget> compileTargets, List<LogMessage> logMessages)
  498. {
  499. CompileTargets = compileTargets;
  500. LogMessages = logMessages;
  501. }
  502. }
  503. public sealed class LogMessage
  504. {
  505. public readonly LogType LogType;
  506. public readonly string Message;
  507. public readonly Exception Exception;
  508. public LogMessage(LogType logType, string message)
  509. {
  510. LogType = logType;
  511. Message = message;
  512. }
  513. public LogMessage(Exception exception)
  514. {
  515. LogType = LogType.Exception;
  516. Exception = exception;
  517. }
  518. }
  519. public enum LogType
  520. {
  521. Warning,
  522. Exception,
  523. }
  524. /// <summary>
  525. /// This method exists solely to ensure that the static constructor has been called.
  526. /// </summary>
  527. public static void EnsureInitialized() { }
  528. #if !UNITY_2019_3_OR_NEWER
  529. // This field is only used for Unity 2018.4.
  530. public static readonly List<System.Reflection.Assembly> AllEditorAssemblies;
  531. #endif
  532. public static readonly List<System.Reflection.Assembly> EditorAssembliesThatCanPossiblyContainJobs;
  533. public static readonly List<System.Reflection.Assembly> EditorAssembliesThatCanPossiblyContainJobsExcludingTestAssemblies;
  534. /// <summary>
  535. /// Collects (and caches) all editor assemblies - transitively.
  536. /// </summary>
  537. static BurstReflection()
  538. {
  539. #if !UNITY_2019_3_OR_NEWER
  540. AllEditorAssemblies = new List<System.Reflection.Assembly>();
  541. #endif
  542. EditorAssembliesThatCanPossiblyContainJobs = new List<System.Reflection.Assembly>();
  543. EditorAssembliesThatCanPossiblyContainJobsExcludingTestAssemblies = new List<System.Reflection.Assembly>();
  544. // TODO: Not sure there is a better way to match assemblies returned by CompilationPipeline.GetAssemblies
  545. // with runtime assemblies contained in the AppDomain.CurrentDomain.GetAssemblies()
  546. // Filter the assemblies
  547. var assemblyList = CompilationPipeline.GetAssemblies(AssembliesType.Editor);
  548. var assemblyNames = new HashSet<string>();
  549. foreach (var assembly in assemblyList)
  550. {
  551. CollectAssemblyNames(assembly, assemblyNames);
  552. }
  553. var allAssemblies = new HashSet<System.Reflection.Assembly>();
  554. foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
  555. {
  556. if (!assemblyNames.Contains(assembly.GetName().Name))
  557. {
  558. continue;
  559. }
  560. CollectAssembly(assembly, allAssemblies);
  561. }
  562. }
  563. // For an assembly to contain something "interesting" when we're scanning for things to compile,
  564. // it needs to either:
  565. // (a) be one of these assemblies, or
  566. // (b) reference one of these assemblies
  567. private static readonly string[] ScanMarkerAssemblies = new[]
  568. {
  569. // Contains [BurstCompile] attribute
  570. "Unity.Burst",
  571. // Contains [JobProducerType] attribute
  572. "UnityEngine.CoreModule"
  573. };
  574. private static void CollectAssembly(System.Reflection.Assembly assembly, HashSet<System.Reflection.Assembly> collect)
  575. {
  576. if (!collect.Add(assembly))
  577. {
  578. return;
  579. }
  580. #if !UNITY_2019_3_OR_NEWER
  581. AllEditorAssemblies.Add(assembly);
  582. #endif
  583. var referencedAssemblies = assembly.GetReferencedAssemblies();
  584. #if UNITY_2019_3_OR_NEWER
  585. var shouldCollectReferences = false;
  586. #endif
  587. var name = assembly.GetName().Name;
  588. if (ScanMarkerAssemblies.Contains(name) || referencedAssemblies.Any(x => ScanMarkerAssemblies.Contains(x.Name)))
  589. {
  590. EditorAssembliesThatCanPossiblyContainJobs.Add(assembly);
  591. #if UNITY_2019_3_OR_NEWER
  592. shouldCollectReferences = true;
  593. #endif
  594. if (!assembly.GetReferencedAssemblies().Any(x => IsNUnitDll(x.Name)))
  595. {
  596. EditorAssembliesThatCanPossiblyContainJobsExcludingTestAssemblies.Add(assembly);
  597. }
  598. }
  599. #if UNITY_2019_3_OR_NEWER
  600. // We can only do this optimization on Unity 2019.3+.
  601. // For Unity 2018.4, we need to populate AllEditorAssemblies
  602. // because it is used in BurstLoader.
  603. if (!shouldCollectReferences)
  604. {
  605. return;
  606. }
  607. #endif
  608. foreach (var assemblyName in referencedAssemblies)
  609. {
  610. try
  611. {
  612. CollectAssembly(System.Reflection.Assembly.Load(assemblyName), collect);
  613. }
  614. catch (Exception)
  615. {
  616. if (BurstLoader.IsDebugging)
  617. {
  618. Debug.LogWarning("Could not load assembly " + assemblyName);
  619. }
  620. }
  621. }
  622. }
  623. private static bool IsNUnitDll(string value)
  624. {
  625. return CultureInfo.InvariantCulture.CompareInfo.IndexOf(value, "nunit.framework") >= 0;
  626. }
  627. private static void CollectAssemblyNames(UnityEditor.Compilation.Assembly assembly, HashSet<string> collect)
  628. {
  629. if (assembly == null || assembly.name == null) return;
  630. if (!collect.Add(assembly.name))
  631. {
  632. return;
  633. }
  634. foreach (var assemblyRef in assembly.assemblyReferences)
  635. {
  636. CollectAssemblyNames(assemblyRef, collect);
  637. }
  638. }
  639. /// <summary>
  640. /// Gets the list of concrete generic type instances used in an assembly.
  641. /// See remarks
  642. /// </summary>
  643. /// <param name="assembly">The assembly</param>
  644. /// <param name="types"></param>
  645. /// <returns>The list of generic type instances</returns>
  646. /// <remarks>
  647. /// Note that this method fetchs only direct type instances but
  648. /// cannot fetch transitive generic type instances.
  649. /// </remarks>
  650. private static void CollectGenericTypeInstances(
  651. System.Reflection.Assembly assembly,
  652. Func<Type, bool> typeFilter,
  653. List<Type> types,
  654. HashSet<Type> visited)
  655. {
  656. // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  657. // WARNING: THIS CODE HAS TO BE MAINTAINED IN SYNC WITH BclApp.cs
  658. // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  659. // From: https://gist.github.com/xoofx/710aaf86e0e8c81649d1261b1ef9590e
  660. if (assembly == null) throw new ArgumentNullException(nameof(assembly));
  661. const int mdMaxCount = 1 << 24;
  662. foreach (var module in assembly.Modules)
  663. {
  664. for (int i = 1; i < mdMaxCount; i++)
  665. {
  666. try
  667. {
  668. // Token base id for TypeSpec
  669. const int mdTypeSpec = 0x1B000000;
  670. var type = module.ResolveType(mdTypeSpec | i);
  671. CollectGenericTypeInstances(type, types, visited, typeFilter);
  672. }
  673. catch (ArgumentOutOfRangeException)
  674. {
  675. break;
  676. }
  677. catch (ArgumentException)
  678. {
  679. // Can happen on ResolveType on certain generic types, so we continue
  680. }
  681. }
  682. for (int i = 1; i < mdMaxCount; i++)
  683. {
  684. try
  685. {
  686. // Token base id for MethodSpec
  687. const int mdMethodSpec = 0x2B000000;
  688. var method = module.ResolveMethod(mdMethodSpec | i);
  689. var genericArgs = method.GetGenericArguments();
  690. foreach (var genArgType in genericArgs)
  691. {
  692. CollectGenericTypeInstances(genArgType, types, visited, typeFilter);
  693. }
  694. }
  695. catch (ArgumentOutOfRangeException)
  696. {
  697. break;
  698. }
  699. catch (ArgumentException)
  700. {
  701. // Can happen on ResolveType on certain generic types, so we continue
  702. }
  703. }
  704. for (int i = 1; i < mdMaxCount; i++)
  705. {
  706. try
  707. {
  708. // Token base id for Field
  709. const int mdField = 0x04000000;
  710. var field = module.ResolveField(mdField | i);
  711. CollectGenericTypeInstances(field.FieldType, types, visited, typeFilter);
  712. }
  713. catch (ArgumentOutOfRangeException)
  714. {
  715. break;
  716. }
  717. catch (ArgumentException)
  718. {
  719. // Can happen on ResolveType on certain generic types, so we continue
  720. }
  721. }
  722. }
  723. // Scan for types used in constructor arguments to assembly-level attributes,
  724. // such as [RegisterGenericJobType(typeof(...))].
  725. foreach (var customAttribute in assembly.CustomAttributes)
  726. {
  727. foreach (var argument in customAttribute.ConstructorArguments)
  728. {
  729. if (argument.ArgumentType == typeof(Type))
  730. {
  731. CollectGenericTypeInstances((Type)argument.Value, types, visited, typeFilter);
  732. }
  733. }
  734. }
  735. }
  736. private static void CollectGenericTypeInstances(
  737. Type type,
  738. List<Type> types,
  739. HashSet<Type> visited,
  740. Func<Type, bool> typeFilter)
  741. {
  742. if (type.IsPrimitive) return;
  743. if (!visited.Add(type)) return;
  744. // Add only concrete types
  745. if (type.IsConstructedGenericType && !type.ContainsGenericParameters && typeFilter(type))
  746. {
  747. types.Add(type);
  748. }
  749. // Collect recursively generic type arguments
  750. var genericTypeArguments = type.GenericTypeArguments;
  751. foreach (var genericTypeArgument in genericTypeArguments)
  752. {
  753. if (!genericTypeArgument.IsPrimitive)
  754. {
  755. CollectGenericTypeInstances(genericTypeArgument, types, visited, typeFilter);
  756. }
  757. }
  758. }
  759. [DebuggerDisplay("{Type} (static methods: {CollectStaticMethods})")]
  760. private struct TypeToVisit
  761. {
  762. public TypeToVisit(Type type, bool collectStaticMethods)
  763. {
  764. Type = type;
  765. CollectStaticMethods = collectStaticMethods;
  766. }
  767. public readonly Type Type;
  768. public readonly bool CollectStaticMethods;
  769. }
  770. }
  771. [Flags]
  772. internal enum BurstReflectionAssemblyOptions
  773. {
  774. None = 0,
  775. ExcludeTestAssemblies = 1,
  776. }
  777. }
  778. #endif