Keine Beschreibung
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

ILPostProcessing.cs 41KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using Mono.Cecil;
  6. using Mono.Cecil.Cil;
  7. namespace zzzUnity.Burst.CodeGen
  8. {
  9. /// <summary>
  10. /// Main class for post processing assemblies. The post processing is currently performing:
  11. /// - Replace C# call from C# to Burst functions with attributes [BurstCompile] to a call to the compiled Burst function
  12. /// In both editor and standalone scenarios. For DOTS Runtime, this is done differently at BclApp level by patching
  13. /// DllImport.
  14. /// - Replace calls to `SharedStatic.GetOrCreate` with `SharedStatic.GetOrCreateUnsafe`, and calculate the hashes during ILPP time
  15. /// rather than in static constructors at runtime.
  16. /// </summary>
  17. internal class ILPostProcessing
  18. {
  19. private AssemblyDefinition _burstAssembly;
  20. private MethodReference _burstCompilerIsEnabledMethodDefinition;
  21. private MethodReference _burstCompilerCompileFunctionPointer;
  22. private FieldReference _burstCompilerOptionsField;
  23. private TypeReference _burstCompilerOptionsType;
  24. private TypeReference _functionPointerType;
  25. private MethodReference _functionPointerGetValue;
  26. private MethodReference _burstDiscardAttributeConstructor;
  27. private TypeSystem _typeSystem;
  28. private TypeReference _systemDelegateType;
  29. private TypeReference _systemASyncCallbackType;
  30. private TypeReference _systemIASyncResultType;
  31. private AssemblyDefinition _assemblyDefinition;
  32. private bool _modified;
  33. #if !UNITY_DOTSPLAYER
  34. private bool _containsDirectCall;
  35. #endif
  36. private readonly StringBuilder _builder = new StringBuilder(1024);
  37. private readonly List<Instruction> _instructionsToReplace = new List<Instruction>(4);
  38. public const string PostfixManaged = "$BurstManaged";
  39. private const string PostfixBurstDirectCall = "$BurstDirectCall";
  40. private const string PostfixBurstDelegate = "$PostfixBurstDelegate";
  41. private const string GetFunctionPointerName = "GetFunctionPointer";
  42. private const string GetFunctionPointerDiscardName = "GetFunctionPointerDiscard";
  43. private const string InvokeName = "Invoke";
  44. public ILPostProcessing(AssemblyResolver loader, bool isForEditor, ErrorDiagnosticDelegate error, LogDelegate log = null, int logLevel = 0, bool skipInitializeOnLoad = false)
  45. {
  46. _skipInitializeOnLoad = skipInitializeOnLoad;
  47. Loader = loader;
  48. IsForEditor = isForEditor;
  49. }
  50. public bool _skipInitializeOnLoad;
  51. public bool IsForEditor { get; private set; }
  52. private AssemblyResolver Loader { get; }
  53. public bool Run(AssemblyDefinition assemblyDefinition)
  54. {
  55. _assemblyDefinition = assemblyDefinition;
  56. _typeSystem = assemblyDefinition.MainModule.TypeSystem;
  57. _modified = false;
  58. var types = assemblyDefinition.MainModule.GetTypes().ToArray();
  59. foreach (var type in types)
  60. {
  61. ProcessType(type);
  62. }
  63. #if !UNITY_DOTSPLAYER
  64. if (_containsDirectCall)
  65. {
  66. GenerateInitializeOnLoadMethod();
  67. }
  68. #endif
  69. return _modified;
  70. }
  71. private void GenerateInitializeOnLoadMethod()
  72. {
  73. // This method is needed to ensure that BurstCompiler.Options is initialized on the main thread,
  74. // before any direct call methods are called on a background thread.
  75. // [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.AfterAssembliesLoaded)]
  76. // [UnityEditor.InitializeOnLoadMethod] // When its an editor assembly
  77. // private static void Initialize()
  78. // {
  79. // var _ = BurstCompiler.Options;
  80. // }
  81. const string initializeOnLoadClassName = "$BurstDirectCallInitializer";
  82. var initializeOnLoadClass = _assemblyDefinition.MainModule.Types.FirstOrDefault(x => x.Name == initializeOnLoadClassName);
  83. if (initializeOnLoadClass != null)
  84. {
  85. // If there's already a class with this name, remove it,
  86. // This would mean that we're postprocessing an already-postprocessed assembly;
  87. // I don't think that ever happens, but no sense in breaking if it does.
  88. _assemblyDefinition.MainModule.Types.Remove(initializeOnLoadClass);
  89. }
  90. initializeOnLoadClass = new TypeDefinition(
  91. "",
  92. initializeOnLoadClassName,
  93. TypeAttributes.NotPublic |
  94. TypeAttributes.AutoLayout |
  95. TypeAttributes.AnsiClass |
  96. TypeAttributes.Abstract |
  97. TypeAttributes.Sealed |
  98. TypeAttributes.BeforeFieldInit)
  99. {
  100. BaseType = _typeSystem.Object
  101. };
  102. _assemblyDefinition.MainModule.Types.Add(initializeOnLoadClass);
  103. var initializeOnLoadMethod = new MethodDefinition("Initialize", MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, _typeSystem.Void)
  104. {
  105. ImplAttributes = MethodImplAttributes.IL | MethodImplAttributes.Managed,
  106. DeclaringType = initializeOnLoadClass
  107. };
  108. initializeOnLoadMethod.Body.Variables.Add(new VariableDefinition(_burstCompilerOptionsType));
  109. var processor = initializeOnLoadMethod.Body.GetILProcessor();
  110. processor.Emit(OpCodes.Ldsfld, _burstCompilerOptionsField);
  111. processor.Emit(OpCodes.Stloc_0);
  112. processor.Emit(OpCodes.Ret);
  113. initializeOnLoadClass.Methods.Add(FixDebugInformation(initializeOnLoadMethod));
  114. var attribute = new CustomAttribute(_unityEngineInitializeOnLoadAttributeCtor);
  115. attribute.ConstructorArguments.Add(new CustomAttributeArgument(_unityEngineRuntimeInitializeLoadType, _unityEngineRuntimeInitializeLoadAfterAssemblies.Constant));
  116. initializeOnLoadMethod.CustomAttributes.Add(attribute);
  117. if (IsForEditor && !_skipInitializeOnLoad)
  118. {
  119. // Need to ensure the editor tag for initialize on load is present, otherwise edit mode tests will not call Initialize
  120. attribute = new CustomAttribute(_unityEditorInitilizeOnLoadAttributeCtor);
  121. initializeOnLoadMethod.CustomAttributes.Add(attribute);
  122. }
  123. }
  124. private static bool CanComputeCompileTimeHash(TypeReference typeRef)
  125. {
  126. if (typeRef.ContainsGenericParameter)
  127. {
  128. return false;
  129. }
  130. var assemblyNameReference = typeRef.Scope as AssemblyNameReference ?? typeRef.Module.Assembly?.Name;
  131. if (assemblyNameReference == null)
  132. {
  133. return false;
  134. }
  135. switch (assemblyNameReference.Name)
  136. {
  137. case "netstandard":
  138. case "mscorlib":
  139. return false;
  140. }
  141. return true;
  142. }
  143. private void ProcessType(TypeDefinition type)
  144. {
  145. if (!type.HasGenericParameters && TryGetBurstCompileAttribute(type, out _))
  146. {
  147. // Make a copy because we are going to modify it
  148. var methodCount = type.Methods.Count;
  149. for (var j = 0; j < methodCount; j++)
  150. {
  151. var method = type.Methods[j];
  152. if (!method.IsStatic || method.HasGenericParameters || !TryGetBurstCompileAttribute(method, out var methodBurstCompileAttribute)) continue;
  153. bool isDirectCallDisabled = false;
  154. bool foundProperty = false;
  155. if (methodBurstCompileAttribute.HasProperties)
  156. {
  157. foreach (var property in methodBurstCompileAttribute.Properties)
  158. {
  159. if (property.Name == "DisableDirectCall")
  160. {
  161. isDirectCallDisabled = (bool)property.Argument.Value;
  162. foundProperty = true;
  163. break;
  164. }
  165. }
  166. }
  167. // If the method doesn't have a direct call specified, try the assembly level, do one last check for any assembly level [BurstCompile] instead.
  168. if (foundProperty == false && TryGetBurstCompileAttribute(method.Module.Assembly, out var assemblyBurstCompileAttribute))
  169. {
  170. if (assemblyBurstCompileAttribute.HasProperties)
  171. {
  172. foreach (var property in assemblyBurstCompileAttribute.Properties)
  173. {
  174. if (property.Name == "DisableDirectCall")
  175. {
  176. isDirectCallDisabled = (bool)property.Argument.Value;
  177. break;
  178. }
  179. }
  180. }
  181. }
  182. foreach (var customAttribute in method.CustomAttributes)
  183. {
  184. if (customAttribute.AttributeType.FullName == "System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute")
  185. {
  186. // Can't / shouldn't enable direct call for [UnmanagedCallersOnly] methods -
  187. // these can't be called from managed code.
  188. isDirectCallDisabled = true;
  189. break;
  190. }
  191. }
  192. #if !UNITY_DOTSPLAYER // Direct call is not Supported for dots runtime via this pre-processor, its handled elsewhere, this code assumes a Unity Editor based burst
  193. if (!isDirectCallDisabled)
  194. {
  195. if (_burstAssembly == null)
  196. {
  197. var resolved = methodBurstCompileAttribute.Constructor.DeclaringType.Resolve();
  198. InitializeBurstAssembly(resolved.Module.Assembly);
  199. }
  200. ProcessMethodForDirectCall(method);
  201. _modified = true;
  202. _containsDirectCall = true;
  203. }
  204. #endif
  205. }
  206. }
  207. if (TypeHasSharedStaticInIt(type))
  208. {
  209. foreach (var method in type.Methods)
  210. {
  211. // Skip anything that isn't the static constructor.
  212. if (method.Name != ".cctor")
  213. {
  214. continue;
  215. }
  216. try
  217. {
  218. #if DEBUG
  219. if (_instructionsToReplace.Count != 0)
  220. {
  221. throw new InvalidOperationException("Instructions to replace wasn't cleared properly!");
  222. }
  223. #endif
  224. foreach (var instruction in method.Body.Instructions)
  225. {
  226. // Skip anything that isn't a call.
  227. if (instruction.OpCode != OpCodes.Call)
  228. {
  229. continue;
  230. }
  231. var calledMethod = (MethodReference)instruction.Operand;
  232. if (calledMethod.Name != "GetOrCreate")
  233. {
  234. continue;
  235. }
  236. // Skip anything that isn't member of the `SharedStatic` class.
  237. if (!TypeIsSharedStatic(calledMethod.DeclaringType))
  238. {
  239. continue;
  240. }
  241. // We only handle the `GetOrCreate` calls with a single parameter (the alignment).
  242. if (calledMethod.Parameters.Count != 1)
  243. {
  244. continue;
  245. }
  246. // We only post-process the generic versions of `GetOrCreate`.
  247. if (!(calledMethod is GenericInstanceMethod genericInstanceMethod))
  248. {
  249. continue;
  250. }
  251. var atLeastOneArgumentCanBeComputed = false;
  252. foreach (var genericArgument in genericInstanceMethod.GenericArguments)
  253. {
  254. if (CanComputeCompileTimeHash(genericArgument))
  255. {
  256. atLeastOneArgumentCanBeComputed = true;
  257. }
  258. }
  259. // We cannot post-process a shared static with all arguments being open generic.
  260. // We cannot post-process a shared static where all of its types are in core libraries.
  261. if (!atLeastOneArgumentCanBeComputed)
  262. {
  263. continue;
  264. }
  265. _instructionsToReplace.Add(instruction);
  266. }
  267. if (_instructionsToReplace.Count > 0)
  268. {
  269. _modified = true;
  270. }
  271. foreach (var instruction in _instructionsToReplace)
  272. {
  273. var calledMethod = (GenericInstanceMethod)instruction.Operand;
  274. var hashCode64 = CalculateHashCode64(calledMethod.GenericArguments[0]);
  275. long subHashCode64 = 0;
  276. var useCalculatedHashCode = true;
  277. var useCalculatedSubHashCode = true;
  278. if (calledMethod.GenericArguments.Count == 2)
  279. {
  280. subHashCode64 = CalculateHashCode64(calledMethod.GenericArguments[1]);
  281. useCalculatedHashCode = CanComputeCompileTimeHash(calledMethod.GenericArguments[0]);
  282. useCalculatedSubHashCode = CanComputeCompileTimeHash(calledMethod.GenericArguments[1]);
  283. }
  284. #if DEBUG
  285. if (!useCalculatedHashCode && !useCalculatedSubHashCode)
  286. {
  287. throw new InvalidOperationException("Cannot replace when both hashes are invalid!");
  288. }
  289. #endif
  290. var methodToCall = "GetOrCreateUnsafe";
  291. TypeReference genericArgument = null;
  292. if (!useCalculatedHashCode)
  293. {
  294. methodToCall = "GetOrCreatePartiallyUnsafeWithSubHashCode";
  295. genericArgument = calledMethod.GenericArguments[0];
  296. }
  297. else if (!useCalculatedSubHashCode)
  298. {
  299. methodToCall = "GetOrCreatePartiallyUnsafeWithHashCode";
  300. genericArgument = calledMethod.GenericArguments[1];
  301. }
  302. var getOrCreateUnsafe = _assemblyDefinition.MainModule.ImportReference(
  303. calledMethod.DeclaringType.Resolve().Methods.First(m => m.Name == methodToCall));
  304. getOrCreateUnsafe.DeclaringType = calledMethod.DeclaringType;
  305. if (genericArgument != null)
  306. {
  307. var genericInstanceMethod = new GenericInstanceMethod(getOrCreateUnsafe);
  308. genericInstanceMethod.GenericArguments.Add(genericArgument);
  309. getOrCreateUnsafe = genericInstanceMethod;
  310. }
  311. var processor = method.Body.GetILProcessor();
  312. if (useCalculatedHashCode)
  313. {
  314. processor.InsertBefore(instruction, processor.Create(OpCodes.Ldc_I8, hashCode64));
  315. }
  316. if (useCalculatedSubHashCode)
  317. {
  318. processor.InsertBefore(instruction, processor.Create(OpCodes.Ldc_I8, subHashCode64));
  319. }
  320. processor.Replace(instruction, processor.Create(OpCodes.Call, getOrCreateUnsafe));
  321. }
  322. }
  323. finally
  324. {
  325. _instructionsToReplace.Clear();
  326. }
  327. }
  328. }
  329. }
  330. // WARNING: This **must** be kept in sync with the definition in BurstRuntime.cs!
  331. private static long HashStringWithFNV1A64(string text)
  332. {
  333. // Using http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-1a
  334. // with basis and prime:
  335. const ulong offsetBasis = 14695981039346656037;
  336. const ulong prime = 1099511628211;
  337. ulong result = offsetBasis;
  338. foreach (var c in text)
  339. {
  340. result = prime * (result ^ (byte)(c & 255));
  341. result = prime * (result ^ (byte)(c >> 8));
  342. }
  343. return (long)result;
  344. }
  345. private long CalculateHashCode64(TypeReference type)
  346. {
  347. try
  348. {
  349. #if DEBUG
  350. if (_builder.Length != 0)
  351. {
  352. throw new InvalidOperationException("StringBuilder wasn't cleared properly!");
  353. }
  354. #endif
  355. type.BuildAssemblyQualifiedName(_builder);
  356. return HashStringWithFNV1A64(_builder.ToString());
  357. }
  358. finally
  359. {
  360. _builder.Clear();
  361. }
  362. }
  363. private static bool TypeIsSharedStatic(TypeReference typeRef)
  364. {
  365. if (typeRef.Namespace != "Unity.Burst")
  366. {
  367. return false;
  368. }
  369. if (typeRef.Name != "SharedStatic`1")
  370. {
  371. return false;
  372. }
  373. return true;
  374. }
  375. private static bool TypeHasSharedStaticInIt(TypeDefinition typeDef)
  376. {
  377. foreach (var field in typeDef.Fields)
  378. {
  379. if (TypeIsSharedStatic(field.FieldType))
  380. {
  381. return true;
  382. }
  383. }
  384. return false;
  385. }
  386. private TypeDefinition InjectDelegate(TypeDefinition declaringType, string originalName, MethodDefinition managed, string uniqueSuffix)
  387. {
  388. var injectedDelegateType = new TypeDefinition(declaringType.Namespace, $"{originalName}{uniqueSuffix}{PostfixBurstDelegate}",
  389. TypeAttributes.NestedPublic |
  390. TypeAttributes.AutoLayout |
  391. TypeAttributes.AnsiClass |
  392. TypeAttributes.Sealed
  393. )
  394. {
  395. DeclaringType = declaringType,
  396. BaseType = _systemDelegateType
  397. };
  398. declaringType.NestedTypes.Add(injectedDelegateType);
  399. {
  400. var constructor = new MethodDefinition(".ctor",
  401. MethodAttributes.Public |
  402. MethodAttributes.HideBySig |
  403. MethodAttributes.SpecialName |
  404. MethodAttributes.RTSpecialName,
  405. _typeSystem.Void)
  406. {
  407. HasThis = true,
  408. IsManaged = true,
  409. IsRuntime = true,
  410. DeclaringType = injectedDelegateType
  411. };
  412. constructor.Parameters.Add(new ParameterDefinition(_typeSystem.Object));
  413. constructor.Parameters.Add(new ParameterDefinition(_typeSystem.IntPtr));
  414. injectedDelegateType.Methods.Add(constructor);
  415. }
  416. {
  417. var invoke = new MethodDefinition("Invoke",
  418. MethodAttributes.Public |
  419. MethodAttributes.HideBySig |
  420. MethodAttributes.NewSlot |
  421. MethodAttributes.Virtual,
  422. managed.ReturnType)
  423. {
  424. HasThis = true,
  425. IsManaged = true,
  426. IsRuntime = true,
  427. DeclaringType = injectedDelegateType
  428. };
  429. foreach (var parameter in managed.Parameters)
  430. {
  431. invoke.Parameters.Add(parameter);
  432. }
  433. injectedDelegateType.Methods.Add(invoke);
  434. }
  435. {
  436. var beginInvoke = new MethodDefinition("BeginInvoke",
  437. MethodAttributes.Public |
  438. MethodAttributes.HideBySig |
  439. MethodAttributes.NewSlot |
  440. MethodAttributes.Virtual,
  441. _systemIASyncResultType)
  442. {
  443. HasThis = true,
  444. IsManaged = true,
  445. IsRuntime = true,
  446. DeclaringType = injectedDelegateType
  447. };
  448. foreach (var parameter in managed.Parameters)
  449. {
  450. beginInvoke.Parameters.Add(parameter);
  451. }
  452. beginInvoke.Parameters.Add(new ParameterDefinition(_systemASyncCallbackType));
  453. beginInvoke.Parameters.Add(new ParameterDefinition(_typeSystem.Object));
  454. injectedDelegateType.Methods.Add(beginInvoke);
  455. }
  456. {
  457. var endInvoke = new MethodDefinition("EndInvoke",
  458. MethodAttributes.Public |
  459. MethodAttributes.HideBySig |
  460. MethodAttributes.NewSlot |
  461. MethodAttributes.Virtual,
  462. managed.ReturnType)
  463. {
  464. HasThis = true,
  465. IsManaged = true,
  466. IsRuntime = true,
  467. DeclaringType = injectedDelegateType
  468. };
  469. endInvoke.Parameters.Add(new ParameterDefinition(_systemIASyncResultType));
  470. injectedDelegateType.Methods.Add(endInvoke);
  471. }
  472. return injectedDelegateType;
  473. }
  474. private MethodDefinition CreateGetFunctionPointerDiscardMethod(TypeDefinition cls, FieldDefinition pointerField, MethodDefinition targetMethod, TypeDefinition injectedDelegate)
  475. {
  476. var genericCompileFunctionPointer = new GenericInstanceMethod(_burstCompilerCompileFunctionPointer);
  477. genericCompileFunctionPointer.GenericArguments.Add(injectedDelegate);
  478. var genericFunctionPointerType = new GenericInstanceType(_functionPointerType);
  479. genericFunctionPointerType.GenericArguments.Add(injectedDelegate);
  480. var genericGetValue = new MethodReference(_functionPointerGetValue.Name, _functionPointerGetValue.ReturnType, genericFunctionPointerType);
  481. foreach (var p in _functionPointerGetValue.Parameters)
  482. {
  483. genericGetValue.Parameters.Add(new ParameterDefinition(p.Name, p.Attributes, p.ParameterType));
  484. }
  485. genericGetValue.HasThis = _functionPointerGetValue.HasThis;
  486. genericGetValue.MetadataToken = _functionPointerGetValue.MetadataToken;
  487. /*var genericGetValue = new Mono.Cecil.GenericInstanceMethod(_functionPointerGetValue)
  488. {
  489. DeclaringType = genericFunctionPointerType
  490. };*/
  491. // Create GetFunctionPointerDiscard method:
  492. //
  493. // [BurstDiscard]
  494. // public static void GetFunctionPointerDiscard(ref IntPtr ptr) {
  495. // if (Pointer == null) {
  496. // Pointer = BurstCompiler.CompileFunctionPointer<InjectedDelegate>(d);
  497. // }
  498. //
  499. // ptr = Pointer
  500. // }
  501. var getFunctionPointerDiscardMethod = new MethodDefinition(GetFunctionPointerDiscardName, MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, _typeSystem.Void)
  502. {
  503. ImplAttributes = MethodImplAttributes.IL | MethodImplAttributes.Managed,
  504. DeclaringType = cls
  505. };
  506. getFunctionPointerDiscardMethod.Body.Variables.Add(new VariableDefinition(genericFunctionPointerType));
  507. getFunctionPointerDiscardMethod.Parameters.Add(new ParameterDefinition(new ByReferenceType(_typeSystem.IntPtr)));
  508. var processor = getFunctionPointerDiscardMethod.Body.GetILProcessor();
  509. processor.Emit(OpCodes.Ldsfld, pointerField);
  510. var branchPosition = processor.Body.Instructions[processor.Body.Instructions.Count - 1];
  511. processor.Emit(OpCodes.Ldnull);
  512. processor.Emit(OpCodes.Ldftn, targetMethod);
  513. processor.Emit(OpCodes.Newobj, injectedDelegate.Methods.First(md => md.IsConstructor && md.Parameters.Count == 2));
  514. processor.Emit(OpCodes.Call, genericCompileFunctionPointer);
  515. processor.Emit(OpCodes.Stloc_0);
  516. processor.Emit(OpCodes.Ldloca, 0);
  517. processor.Emit(OpCodes.Call, genericGetValue);
  518. processor.Emit(OpCodes.Stsfld, pointerField);
  519. processor.Emit(OpCodes.Ldarg_0);
  520. processor.InsertAfter(branchPosition, Instruction.Create(OpCodes.Brtrue, processor.Body.Instructions[processor.Body.Instructions.Count - 1]));
  521. processor.Emit(OpCodes.Ldsfld, pointerField);
  522. processor.Emit(OpCodes.Stind_I);
  523. processor.Emit(OpCodes.Ret);
  524. cls.Methods.Add(FixDebugInformation(getFunctionPointerDiscardMethod));
  525. getFunctionPointerDiscardMethod.CustomAttributes.Add(new CustomAttribute(_burstDiscardAttributeConstructor));
  526. return getFunctionPointerDiscardMethod;
  527. }
  528. private MethodDefinition CreateGetFunctionPointerMethod(TypeDefinition cls, MethodDefinition getFunctionPointerDiscardMethod)
  529. {
  530. // Create GetFunctionPointer method:
  531. //
  532. // public static IntPtr GetFunctionPointer() {
  533. // var ptr;
  534. // GetFunctionPointerDiscard(ref ptr);
  535. // return ptr;
  536. // }
  537. var getFunctionPointerMethod = new MethodDefinition(GetFunctionPointerName, MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, _typeSystem.IntPtr)
  538. {
  539. ImplAttributes = MethodImplAttributes.IL | MethodImplAttributes.Managed,
  540. DeclaringType = cls
  541. };
  542. getFunctionPointerMethod.Body.Variables.Add(new VariableDefinition(_typeSystem.IntPtr));
  543. getFunctionPointerMethod.Body.InitLocals = true;
  544. var processor = getFunctionPointerMethod.Body.GetILProcessor();
  545. processor.Emit(OpCodes.Ldc_I4_0);
  546. processor.Emit(OpCodes.Conv_I);
  547. processor.Emit(OpCodes.Stloc_0);
  548. processor.Emit(OpCodes.Ldloca_S, (byte)0);
  549. processor.Emit(OpCodes.Call, getFunctionPointerDiscardMethod);
  550. processor.Emit(OpCodes.Ldloc_0);
  551. processor.Emit(OpCodes.Ret);
  552. cls.Methods.Add(FixDebugInformation(getFunctionPointerMethod));
  553. return getFunctionPointerMethod;
  554. }
  555. private void ProcessMethodForDirectCall(MethodDefinition burstCompileMethod)
  556. {
  557. var declaringType = burstCompileMethod.DeclaringType;
  558. var uniqueSuffix = $"_{burstCompileMethod.MetadataToken.RID:X8}";
  559. var injectedDelegate = InjectDelegate(declaringType, burstCompileMethod.Name, burstCompileMethod, uniqueSuffix);
  560. // Create a copy of the original method that will be the actual managed method
  561. // The original method is patched at the end of this method to call
  562. // the dispatcher that will go to the Burst implementation or the managed method (if in the editor and Burst is disabled)
  563. var managedFallbackMethod = new MethodDefinition($"{burstCompileMethod.Name}{PostfixManaged}", burstCompileMethod.Attributes, burstCompileMethod.ReturnType)
  564. {
  565. DeclaringType = declaringType,
  566. ImplAttributes = burstCompileMethod.ImplAttributes,
  567. MetadataToken = burstCompileMethod.MetadataToken,
  568. };
  569. // Ensure the CustomAttributes are the same
  570. managedFallbackMethod.CustomAttributes.Clear();
  571. foreach (var attr in burstCompileMethod.CustomAttributes)
  572. {
  573. managedFallbackMethod.CustomAttributes.Add(attr);
  574. }
  575. declaringType.Methods.Add(managedFallbackMethod);
  576. foreach (var parameter in burstCompileMethod.Parameters)
  577. {
  578. managedFallbackMethod.Parameters.Add(parameter);
  579. }
  580. // Copy the body from the original burst method to the managed fallback, we'll replace the burstCompileMethod body later.
  581. managedFallbackMethod.Body.InitLocals = burstCompileMethod.Body.InitLocals;
  582. managedFallbackMethod.Body.LocalVarToken = burstCompileMethod.Body.LocalVarToken;
  583. managedFallbackMethod.Body.MaxStackSize = burstCompileMethod.Body.MaxStackSize;
  584. foreach (var variable in burstCompileMethod.Body.Variables)
  585. {
  586. managedFallbackMethod.Body.Variables.Add(variable);
  587. }
  588. foreach (var instruction in burstCompileMethod.Body.Instructions)
  589. {
  590. managedFallbackMethod.Body.Instructions.Add(instruction);
  591. }
  592. foreach (var exceptionHandler in burstCompileMethod.Body.ExceptionHandlers)
  593. {
  594. managedFallbackMethod.Body.ExceptionHandlers.Add(exceptionHandler);
  595. }
  596. managedFallbackMethod.ImplAttributes &= MethodImplAttributes.NoInlining;
  597. // 0x0100 is AggressiveInlining
  598. managedFallbackMethod.ImplAttributes |= (MethodImplAttributes)0x0100;
  599. // The method needs to be public because we query for it in the ILPP code.
  600. managedFallbackMethod.Attributes &= ~MethodAttributes.Private;
  601. managedFallbackMethod.Attributes |= MethodAttributes.Public;
  602. // private static class (Name_RID.$Postfix)
  603. var cls = new TypeDefinition(declaringType.Namespace, $"{burstCompileMethod.Name}{uniqueSuffix}{PostfixBurstDirectCall}",
  604. TypeAttributes.NestedAssembly |
  605. TypeAttributes.AutoLayout |
  606. TypeAttributes.AnsiClass |
  607. TypeAttributes.Abstract |
  608. TypeAttributes.Sealed |
  609. TypeAttributes.BeforeFieldInit
  610. )
  611. {
  612. DeclaringType = declaringType,
  613. BaseType = _typeSystem.Object
  614. };
  615. declaringType.NestedTypes.Add(cls);
  616. // Create Field:
  617. //
  618. // private static IntPtr Pointer;
  619. var pointerField = new FieldDefinition("Pointer", FieldAttributes.Static | FieldAttributes.Private, _typeSystem.IntPtr)
  620. {
  621. DeclaringType = cls
  622. };
  623. cls.Fields.Add(pointerField);
  624. var getFunctionPointerDiscardMethod = CreateGetFunctionPointerDiscardMethod(
  625. cls, pointerField,
  626. // In the player the function pointer is looked up in a registry by name
  627. // so we can't request a `$BurstManaged` function (because it was never compiled, only the toplevel one)
  628. // But, it's safe *in the player* to request the toplevel function
  629. IsForEditor ? managedFallbackMethod : burstCompileMethod,
  630. injectedDelegate);
  631. var getFunctionPointerMethod = CreateGetFunctionPointerMethod(cls, getFunctionPointerDiscardMethod);
  632. // Create the Invoke method based on the original method (same signature)
  633. //
  634. // public static XXX Invoke(...args) {
  635. // if (BurstCompiler.IsEnabled)
  636. // {
  637. // var funcPtr = GetFunctionPointer();
  638. // if (funcPtr != null) return funcPtr(...args);
  639. // }
  640. // return OriginalMethod(...args);
  641. // }
  642. var invokeAttributes = managedFallbackMethod.Attributes;
  643. invokeAttributes &= ~MethodAttributes.Private;
  644. invokeAttributes |= MethodAttributes.Public;
  645. var invoke = new MethodDefinition(InvokeName, invokeAttributes, burstCompileMethod.ReturnType)
  646. {
  647. ImplAttributes = MethodImplAttributes.IL | MethodImplAttributes.Managed,
  648. DeclaringType = cls
  649. };
  650. var signature = new CallSite(burstCompileMethod.ReturnType)
  651. {
  652. CallingConvention = MethodCallingConvention.C
  653. };
  654. foreach (var parameter in burstCompileMethod.Parameters)
  655. {
  656. invoke.Parameters.Add(parameter);
  657. signature.Parameters.Add(parameter);
  658. }
  659. invoke.Body.Variables.Add(new VariableDefinition(_typeSystem.IntPtr));
  660. invoke.Body.InitLocals = true;
  661. var processor = invoke.Body.GetILProcessor();
  662. processor.Emit(OpCodes.Call, _burstCompilerIsEnabledMethodDefinition);
  663. var branchPosition0 = processor.Body.Instructions[processor.Body.Instructions.Count - 1];
  664. processor.Emit(OpCodes.Call, getFunctionPointerMethod);
  665. processor.Emit(OpCodes.Stloc_0);
  666. processor.Emit(OpCodes.Ldloc_0);
  667. var branchPosition1 = processor.Body.Instructions[processor.Body.Instructions.Count - 1];
  668. EmitArguments(processor, invoke);
  669. processor.Emit(OpCodes.Ldloc_0);
  670. processor.Emit(OpCodes.Calli, signature);
  671. processor.Emit(OpCodes.Ret);
  672. var previousRet = processor.Body.Instructions[processor.Body.Instructions.Count - 1];
  673. EmitArguments(processor, invoke);
  674. processor.Emit(OpCodes.Call, managedFallbackMethod);
  675. processor.Emit(OpCodes.Ret);
  676. // Insert the branch once we have emitted the instructions
  677. processor.InsertAfter(branchPosition0, Instruction.Create(OpCodes.Brfalse, previousRet.Next));
  678. processor.InsertAfter(branchPosition1, Instruction.Create(OpCodes.Brfalse, previousRet.Next));
  679. cls.Methods.Add(FixDebugInformation(invoke));
  680. // Final patching of the original method
  681. // public static XXX OriginalMethod(...args) {
  682. // Name_RID.$Postfix.Invoke(...args);
  683. // ret;
  684. // }
  685. burstCompileMethod.Body = new MethodBody(burstCompileMethod);
  686. processor = burstCompileMethod.Body.GetILProcessor();
  687. EmitArguments(processor, burstCompileMethod);
  688. processor.Emit(OpCodes.Call, invoke);
  689. processor.Emit(OpCodes.Ret);
  690. FixDebugInformation(burstCompileMethod);
  691. }
  692. private static MethodDefinition FixDebugInformation(MethodDefinition method)
  693. {
  694. method.DebugInformation.Scope = new ScopeDebugInformation(method.Body.Instructions.First(), method.Body.Instructions.Last());
  695. return method;
  696. }
  697. private AssemblyDefinition GetAsmDefinitionFromFile(AssemblyResolver loader, string assemblyName)
  698. {
  699. if (loader.TryResolve(AssemblyNameReference.Parse(assemblyName), out var result))
  700. {
  701. return result;
  702. }
  703. return null;
  704. }
  705. private MethodReference _unityEngineInitializeOnLoadAttributeCtor;
  706. private TypeReference _unityEngineRuntimeInitializeLoadType;
  707. private FieldDefinition _unityEngineRuntimeInitializeLoadAfterAssemblies;
  708. private MethodReference _unityEditorInitilizeOnLoadAttributeCtor;
  709. private void InitializeBurstAssembly(AssemblyDefinition burstAssembly)
  710. {
  711. _burstAssembly = burstAssembly;
  712. var burstCompilerTypeDefinition = burstAssembly.MainModule.GetType("Unity.Burst", "BurstCompiler");
  713. _burstCompilerIsEnabledMethodDefinition = _assemblyDefinition.MainModule.ImportReference(burstCompilerTypeDefinition.Methods.FirstOrDefault(x => x.Name == "get_IsEnabled"));
  714. _burstCompilerCompileFunctionPointer = _assemblyDefinition.MainModule.ImportReference(burstCompilerTypeDefinition.Methods.FirstOrDefault(x => x.Name == "CompileFunctionPointer"));
  715. _burstCompilerOptionsField = _assemblyDefinition.MainModule.ImportReference(burstCompilerTypeDefinition.Fields.FirstOrDefault(x => x.Name == "Options"));
  716. _burstCompilerOptionsType = _assemblyDefinition.MainModule.ImportReference(burstAssembly.MainModule.GetType("Unity.Burst", "BurstCompilerOptions"));
  717. var functionPointerTypeDefinition = burstAssembly.MainModule.GetType("Unity.Burst", "FunctionPointer`1");
  718. _functionPointerType = _assemblyDefinition.MainModule.ImportReference(functionPointerTypeDefinition);
  719. _functionPointerGetValue = _assemblyDefinition.MainModule.ImportReference(functionPointerTypeDefinition.Methods.FirstOrDefault(x => x.Name == "get_Value"));
  720. var corLibrary = Loader.Resolve((AssemblyNameReference)_typeSystem.CoreLibrary);
  721. _systemDelegateType = _assemblyDefinition.MainModule.ImportReference(corLibrary.MainModule.GetType("System.MulticastDelegate"));
  722. _systemASyncCallbackType = _assemblyDefinition.MainModule.ImportReference(corLibrary.MainModule.GetType("System.AsyncCallback"));
  723. _systemIASyncResultType = _assemblyDefinition.MainModule.ImportReference(corLibrary.MainModule.GetType("System.IAsyncResult"));
  724. var asmDef = GetAsmDefinitionFromFile(Loader, "UnityEngine.CoreModule");
  725. var runtimeInitializeOnLoadMethodAttribute = asmDef.MainModule.GetType("UnityEngine", "RuntimeInitializeOnLoadMethodAttribute");
  726. var runtimeInitializeLoadType = asmDef.MainModule.GetType("UnityEngine", "RuntimeInitializeLoadType");
  727. var burstDiscardType = asmDef.MainModule.GetType("Unity.Burst", "BurstDiscardAttribute");
  728. _burstDiscardAttributeConstructor = _assemblyDefinition.MainModule.ImportReference(burstDiscardType.Methods.First(method => method.Name == ".ctor"));
  729. _unityEngineInitializeOnLoadAttributeCtor = _assemblyDefinition.MainModule.ImportReference(runtimeInitializeOnLoadMethodAttribute.Methods.FirstOrDefault(x => x.Name == ".ctor" && x.HasParameters));
  730. _unityEngineRuntimeInitializeLoadType = _assemblyDefinition.MainModule.ImportReference(runtimeInitializeLoadType);
  731. _unityEngineRuntimeInitializeLoadAfterAssemblies = runtimeInitializeLoadType.Fields.FirstOrDefault(x => x.Name == "AfterAssembliesLoaded");
  732. if (IsForEditor && !_skipInitializeOnLoad)
  733. {
  734. asmDef = GetAsmDefinitionFromFile(Loader, "UnityEditor.CoreModule");
  735. if (asmDef == null)
  736. asmDef = GetAsmDefinitionFromFile(Loader, "UnityEditor");
  737. var initializeOnLoadMethodAttribute = asmDef.MainModule.GetType("UnityEditor", "InitializeOnLoadMethodAttribute");
  738. _unityEditorInitilizeOnLoadAttributeCtor = _assemblyDefinition.MainModule.ImportReference(initializeOnLoadMethodAttribute.Methods.FirstOrDefault(x => x.Name == ".ctor" && !x.HasParameters));
  739. }
  740. }
  741. private static void EmitArguments(ILProcessor processor, MethodDefinition method)
  742. {
  743. for (var i = 0; i < method.Parameters.Count; i++)
  744. {
  745. switch (i)
  746. {
  747. case 0:
  748. processor.Emit(OpCodes.Ldarg_0);
  749. break;
  750. case 1:
  751. processor.Emit(OpCodes.Ldarg_1);
  752. break;
  753. case 2:
  754. processor.Emit(OpCodes.Ldarg_2);
  755. break;
  756. case 3:
  757. processor.Emit(OpCodes.Ldarg_3);
  758. break;
  759. default:
  760. if (i <= 255)
  761. {
  762. processor.Emit(OpCodes.Ldarg_S, (byte)i);
  763. }
  764. else
  765. {
  766. processor.Emit(OpCodes.Ldarg, i);
  767. }
  768. break;
  769. }
  770. }
  771. }
  772. private static bool TryGetBurstCompileAttribute(ICustomAttributeProvider provider, out CustomAttribute customAttribute)
  773. {
  774. if (provider.HasCustomAttributes)
  775. {
  776. foreach (var customAttr in provider.CustomAttributes)
  777. {
  778. if (customAttr.Constructor.DeclaringType.Name == "BurstCompileAttribute")
  779. {
  780. customAttribute = customAttr;
  781. return true;
  782. }
  783. }
  784. }
  785. customAttribute = null;
  786. return false;
  787. }
  788. }
  789. }