Няма описание
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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  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. public static FindExecuteMethodsResult FindExecuteMethods(List<System.Reflection.Assembly> assemblyList, BurstReflectionAssemblyOptions options)
  22. {
  23. var methodsToCompile = new List<BurstCompileTarget>();
  24. var methodsToCompileSet = new HashSet<MethodInfo>();
  25. var logMessages = new List<LogMessage>();
  26. var interfaceToProducer = new Dictionary<Type, Type>();
  27. var assemblySet = new HashSet<System.Reflection.Assembly>(assemblyList);
  28. void AddTarget(BurstCompileTarget target)
  29. {
  30. if (target.Method.Name.EndsWith("$BurstManaged")) return;
  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. public sealed class FindExecuteMethodsResult
  302. {
  303. public readonly List<BurstCompileTarget> CompileTargets;
  304. public readonly List<LogMessage> LogMessages;
  305. public FindExecuteMethodsResult(List<BurstCompileTarget> compileTargets, List<LogMessage> logMessages)
  306. {
  307. CompileTargets = compileTargets;
  308. LogMessages = logMessages;
  309. }
  310. }
  311. public sealed class LogMessage
  312. {
  313. public readonly LogType LogType;
  314. public readonly string Message;
  315. public readonly Exception Exception;
  316. public LogMessage(LogType logType, string message)
  317. {
  318. LogType = logType;
  319. Message = message;
  320. }
  321. public LogMessage(Exception exception)
  322. {
  323. LogType = LogType.Exception;
  324. Exception = exception;
  325. }
  326. }
  327. public enum LogType
  328. {
  329. Warning,
  330. Exception,
  331. }
  332. /// <summary>
  333. /// This method exists solely to ensure that the static constructor has been called.
  334. /// </summary>
  335. public static void EnsureInitialized() { }
  336. public static readonly List<System.Reflection.Assembly> EditorAssembliesThatCanPossiblyContainJobs;
  337. public static readonly List<System.Reflection.Assembly> EditorAssembliesThatCanPossiblyContainJobsExcludingTestAssemblies;
  338. /// <summary>
  339. /// Collects (and caches) all editor assemblies - transitively.
  340. /// </summary>
  341. static BurstReflection()
  342. {
  343. EditorAssembliesThatCanPossiblyContainJobs = new List<System.Reflection.Assembly>();
  344. EditorAssembliesThatCanPossiblyContainJobsExcludingTestAssemblies = new List<System.Reflection.Assembly>();
  345. // TODO: Not sure there is a better way to match assemblies returned by CompilationPipeline.GetAssemblies
  346. // with runtime assemblies contained in the AppDomain.CurrentDomain.GetAssemblies()
  347. // Filter the assemblies
  348. var assemblyList = CompilationPipeline.GetAssemblies(AssembliesType.Editor);
  349. var assemblyNames = new HashSet<string>();
  350. foreach (var assembly in assemblyList)
  351. {
  352. CollectAssemblyNames(assembly, assemblyNames);
  353. }
  354. var allAssemblies = new HashSet<System.Reflection.Assembly>();
  355. foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
  356. {
  357. if (!assemblyNames.Contains(assembly.GetName().Name))
  358. {
  359. continue;
  360. }
  361. CollectAssembly(assembly, allAssemblies);
  362. }
  363. }
  364. // For an assembly to contain something "interesting" when we're scanning for things to compile,
  365. // it needs to either:
  366. // (a) be one of these assemblies, or
  367. // (b) reference one of these assemblies
  368. private static readonly string[] ScanMarkerAssemblies = new[]
  369. {
  370. // Contains [BurstCompile] attribute
  371. "Unity.Burst",
  372. // Contains [JobProducerType] attribute
  373. "UnityEngine.CoreModule"
  374. };
  375. private static void CollectAssembly(System.Reflection.Assembly assembly, HashSet<System.Reflection.Assembly> collect)
  376. {
  377. if (!collect.Add(assembly))
  378. {
  379. return;
  380. }
  381. var referencedAssemblies = assembly.GetReferencedAssemblies();
  382. var shouldCollectReferences = false;
  383. var name = assembly.GetName().Name;
  384. if (ScanMarkerAssemblies.Contains(name) || referencedAssemblies.Any(x => ScanMarkerAssemblies.Contains(x.Name)))
  385. {
  386. EditorAssembliesThatCanPossiblyContainJobs.Add(assembly);
  387. shouldCollectReferences = true;
  388. if (!assembly.GetReferencedAssemblies().Any(x => IsNUnitDll(x.Name)))
  389. {
  390. EditorAssembliesThatCanPossiblyContainJobsExcludingTestAssemblies.Add(assembly);
  391. }
  392. }
  393. if (!shouldCollectReferences)
  394. {
  395. return;
  396. }
  397. foreach (var assemblyName in referencedAssemblies)
  398. {
  399. try
  400. {
  401. CollectAssembly(System.Reflection.Assembly.Load(assemblyName), collect);
  402. }
  403. catch (Exception)
  404. {
  405. if (BurstLoader.IsDebugging)
  406. {
  407. Debug.LogWarning("Could not load assembly " + assemblyName);
  408. }
  409. }
  410. }
  411. }
  412. private static bool IsNUnitDll(string value)
  413. {
  414. return CultureInfo.InvariantCulture.CompareInfo.IndexOf(value, "nunit.framework") >= 0;
  415. }
  416. private static void CollectAssemblyNames(UnityEditor.Compilation.Assembly assembly, HashSet<string> collect)
  417. {
  418. if (assembly == null || assembly.name == null) return;
  419. if (!collect.Add(assembly.name))
  420. {
  421. return;
  422. }
  423. foreach (var assemblyRef in assembly.assemblyReferences)
  424. {
  425. CollectAssemblyNames(assemblyRef, collect);
  426. }
  427. }
  428. /// <summary>
  429. /// Gets the list of concrete generic type instances used in an assembly.
  430. /// See remarks
  431. /// </summary>
  432. /// <param name="assembly">The assembly</param>
  433. /// <param name="types"></param>
  434. /// <returns>The list of generic type instances</returns>
  435. /// <remarks>
  436. /// Note that this method fetchs only direct type instances but
  437. /// cannot fetch transitive generic type instances.
  438. /// </remarks>
  439. private static void CollectGenericTypeInstances(
  440. System.Reflection.Assembly assembly,
  441. Func<Type, bool> typeFilter,
  442. List<Type> types,
  443. HashSet<Type> visited)
  444. {
  445. // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  446. // WARNING: THIS CODE HAS TO BE MAINTAINED IN SYNC WITH BclApp.cs
  447. // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  448. // From: https://gist.github.com/xoofx/710aaf86e0e8c81649d1261b1ef9590e
  449. if (assembly == null) throw new ArgumentNullException(nameof(assembly));
  450. const int mdMaxCount = 1 << 24;
  451. foreach (var module in assembly.Modules)
  452. {
  453. for (int i = 1; i < mdMaxCount; i++)
  454. {
  455. try
  456. {
  457. // Token base id for TypeSpec
  458. const int mdTypeSpec = 0x1B000000;
  459. var type = module.ResolveType(mdTypeSpec | i);
  460. CollectGenericTypeInstances(type, types, visited, typeFilter);
  461. }
  462. catch (ArgumentOutOfRangeException)
  463. {
  464. break;
  465. }
  466. catch (ArgumentException)
  467. {
  468. // Can happen on ResolveType on certain generic types, so we continue
  469. }
  470. }
  471. for (int i = 1; i < mdMaxCount; i++)
  472. {
  473. try
  474. {
  475. // Token base id for MethodSpec
  476. const int mdMethodSpec = 0x2B000000;
  477. var method = module.ResolveMethod(mdMethodSpec | i);
  478. var genericArgs = method.GetGenericArguments();
  479. foreach (var genArgType in genericArgs)
  480. {
  481. CollectGenericTypeInstances(genArgType, types, visited, typeFilter);
  482. }
  483. }
  484. catch (ArgumentOutOfRangeException)
  485. {
  486. break;
  487. }
  488. catch (ArgumentException)
  489. {
  490. // Can happen on ResolveType on certain generic types, so we continue
  491. }
  492. }
  493. for (int i = 1; i < mdMaxCount; i++)
  494. {
  495. try
  496. {
  497. // Token base id for Field
  498. const int mdField = 0x04000000;
  499. var field = module.ResolveField(mdField | i);
  500. CollectGenericTypeInstances(field.FieldType, types, visited, typeFilter);
  501. }
  502. catch (ArgumentOutOfRangeException)
  503. {
  504. break;
  505. }
  506. catch (ArgumentException)
  507. {
  508. // Can happen on ResolveType on certain generic types, so we continue
  509. }
  510. }
  511. }
  512. // Scan for types used in constructor arguments to assembly-level attributes,
  513. // such as [RegisterGenericJobType(typeof(...))].
  514. foreach (var customAttribute in assembly.CustomAttributes)
  515. {
  516. foreach (var argument in customAttribute.ConstructorArguments)
  517. {
  518. if (argument.ArgumentType == typeof(Type))
  519. {
  520. CollectGenericTypeInstances((Type)argument.Value, types, visited, typeFilter);
  521. }
  522. }
  523. }
  524. }
  525. private static void CollectGenericTypeInstances(
  526. Type type,
  527. List<Type> types,
  528. HashSet<Type> visited,
  529. Func<Type, bool> typeFilter)
  530. {
  531. if (type.IsPrimitive) return;
  532. if (!visited.Add(type)) return;
  533. // Add only concrete types
  534. if (type.IsConstructedGenericType && !type.ContainsGenericParameters && typeFilter(type))
  535. {
  536. types.Add(type);
  537. }
  538. // Collect recursively generic type arguments
  539. var genericTypeArguments = type.GenericTypeArguments;
  540. foreach (var genericTypeArgument in genericTypeArguments)
  541. {
  542. if (!genericTypeArgument.IsPrimitive)
  543. {
  544. CollectGenericTypeInstances(genericTypeArgument, types, visited, typeFilter);
  545. }
  546. }
  547. }
  548. [DebuggerDisplay("{Type} (static methods: {CollectStaticMethods})")]
  549. private struct TypeToVisit
  550. {
  551. public TypeToVisit(Type type, bool collectStaticMethods)
  552. {
  553. Type = type;
  554. CollectStaticMethods = collectStaticMethods;
  555. }
  556. public readonly Type Type;
  557. public readonly bool CollectStaticMethods;
  558. }
  559. }
  560. [Flags]
  561. internal enum BurstReflectionAssemblyOptions
  562. {
  563. None = 0,
  564. ExcludeTestAssemblies = 1,
  565. }
  566. }
  567. #endif