Sin descripción
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.

ILPostProcessingLegacy.cs 45KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999
  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. internal delegate void LogDelegate(string message);
  10. internal delegate void ErrorDiagnosticDelegate(MethodDefinition method, Instruction instruction, string message);
  11. /// <summary>
  12. /// Main class for post processing assemblies. The post processing is currently performing:
  13. /// - Replace C# call from C# to Burst functions with attributes [BurstCompile] to a call to the compiled Burst function
  14. /// In both editor and standalone scenarios. For DOTS Runtime, this is done differently at BclApp level by patching
  15. /// DllImport.
  16. /// - Replace calls to `SharedStatic.GetOrCreate` with `SharedStatic.GetOrCreateUnsafe`, and calculate the hashes during ILPP time
  17. /// rather than in static constructors at runtime.
  18. /// </summary>
  19. internal class ILPostProcessingLegacy
  20. {
  21. private AssemblyDefinition _burstAssembly;
  22. private TypeDefinition _burstCompilerTypeDefinition;
  23. private MethodReference _burstCompilerIsEnabledMethodDefinition;
  24. private MethodReference _burstCompilerCompileILPPMethod;
  25. private MethodReference _burstCompilerGetILPPMethodFunctionPointer;
  26. private MethodReference _burstDiscardAttributeConstructor;
  27. private MethodReference _burstCompilerCompileUnsafeStaticMethodReinitialiseAttributeCtor;
  28. private TypeSystem _typeSystem;
  29. private TypeReference _systemType;
  30. private TypeReference _systemDelegateType;
  31. private TypeReference _systemASyncCallbackType;
  32. private TypeReference _systemIASyncResultType;
  33. private AssemblyDefinition _assemblyDefinition;
  34. private bool _modified;
  35. private readonly StringBuilder _builder = new StringBuilder(1024);
  36. private readonly List<Instruction> _instructionsToReplace = new List<Instruction>(4);
  37. private readonly List<MethodDefinition> _directCallInitializeMethods = new List<MethodDefinition>();
  38. private const string PostfixBurstDirectCall = "$BurstDirectCall";
  39. private const string PostfixBurstDelegate = "$PostfixBurstDelegate";
  40. private const string PostfixManaged = "$BurstManaged";
  41. private const string GetFunctionPointerName = "GetFunctionPointer";
  42. private const string GetFunctionPointerDiscardName = "GetFunctionPointerDiscard";
  43. private const string InvokeName = "Invoke";
  44. public ILPostProcessingLegacy(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 we processed any direct-calls, then generate a single [RuntimeInitializeOnLoadMethod] method
  64. // for the whole assembly, which will initialize each individual direct-call class.
  65. if (_directCallInitializeMethods.Count > 0)
  66. {
  67. GenerateInitializeOnLoadMethod();
  68. }
  69. return _modified;
  70. }
  71. private void GenerateInitializeOnLoadMethod()
  72. {
  73. // [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.AfterAssembliesLoaded)]
  74. // [UnityEditor.InitializeOnLoadMethod] // When its an editor assembly
  75. // private static void Initialize()
  76. // {
  77. // DirectCallA.Initialize();
  78. // DirectCallB.Initialize();
  79. // }
  80. const string initializeOnLoadClassName = "$BurstDirectCallInitializer";
  81. var initializeOnLoadClass = _assemblyDefinition.MainModule.Types.FirstOrDefault(x => x.Name == initializeOnLoadClassName);
  82. if (initializeOnLoadClass != null)
  83. {
  84. // If there's already a class with this name, remove it,
  85. // This would mean that we're postprocessing an already-postprocessed assembly;
  86. // I don't think that ever happens, but no sense in breaking if it does.
  87. _assemblyDefinition.MainModule.Types.Remove(initializeOnLoadClass);
  88. }
  89. initializeOnLoadClass = new TypeDefinition(
  90. "",
  91. initializeOnLoadClassName,
  92. TypeAttributes.NotPublic |
  93. TypeAttributes.AutoLayout |
  94. TypeAttributes.AnsiClass |
  95. TypeAttributes.Abstract |
  96. TypeAttributes.Sealed |
  97. TypeAttributes.BeforeFieldInit)
  98. {
  99. BaseType = _typeSystem.Object
  100. };
  101. _assemblyDefinition.MainModule.Types.Add(initializeOnLoadClass);
  102. var initializeOnLoadMethod = new MethodDefinition("Initialize", MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, _typeSystem.Void)
  103. {
  104. ImplAttributes = MethodImplAttributes.IL | MethodImplAttributes.Managed,
  105. DeclaringType = initializeOnLoadClass
  106. };
  107. var processor = initializeOnLoadMethod.Body.GetILProcessor();
  108. foreach (var initializeMethod in _directCallInitializeMethods)
  109. {
  110. processor.Emit(OpCodes.Call, initializeMethod);
  111. }
  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 && TryGetBurstCompilerAttribute(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 || !TryGetBurstCompilerAttribute(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 && TryGetBurstCompilerAttribute(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. }
  203. #endif
  204. }
  205. }
  206. if (TypeHasSharedStaticInIt(type))
  207. {
  208. foreach (var method in type.Methods)
  209. {
  210. // Skip anything that isn't the static constructor.
  211. if (method.Name != ".cctor")
  212. {
  213. continue;
  214. }
  215. try
  216. {
  217. #if DEBUG
  218. if (_instructionsToReplace.Count != 0)
  219. {
  220. throw new InvalidOperationException("Instructions to replace wasn't cleared properly!");
  221. }
  222. #endif
  223. foreach (var instruction in method.Body.Instructions)
  224. {
  225. // Skip anything that isn't a call.
  226. if (instruction.OpCode != OpCodes.Call)
  227. {
  228. continue;
  229. }
  230. var calledMethod = (MethodReference)instruction.Operand;
  231. if (calledMethod.Name != "GetOrCreate")
  232. {
  233. continue;
  234. }
  235. // Skip anything that isn't member of the `SharedStatic` class.
  236. if (!TypeIsSharedStatic(calledMethod.DeclaringType))
  237. {
  238. continue;
  239. }
  240. // We only handle the `GetOrCreate` calls with a single parameter (the alignment).
  241. if (calledMethod.Parameters.Count != 1)
  242. {
  243. continue;
  244. }
  245. // We only post-process the generic versions of `GetOrCreate`.
  246. if (!(calledMethod is GenericInstanceMethod genericInstanceMethod))
  247. {
  248. continue;
  249. }
  250. var atLeastOneArgumentCanBeComputed = false;
  251. foreach (var genericArgument in genericInstanceMethod.GenericArguments)
  252. {
  253. if (CanComputeCompileTimeHash(genericArgument))
  254. {
  255. atLeastOneArgumentCanBeComputed = true;
  256. }
  257. }
  258. // We cannot post-process a shared static with all arguments being open generic.
  259. // We cannot post-process a shared static where all of its types are in core libraries.
  260. if (!atLeastOneArgumentCanBeComputed)
  261. {
  262. continue;
  263. }
  264. _instructionsToReplace.Add(instruction);
  265. }
  266. if (_instructionsToReplace.Count > 0)
  267. {
  268. _modified = true;
  269. }
  270. foreach (var instruction in _instructionsToReplace)
  271. {
  272. var calledMethod = (GenericInstanceMethod)instruction.Operand;
  273. var hashCode64 = CalculateHashCode64(calledMethod.GenericArguments[0]);
  274. long subHashCode64 = 0;
  275. var useCalculatedHashCode = true;
  276. var useCalculatedSubHashCode = true;
  277. if (calledMethod.GenericArguments.Count == 2)
  278. {
  279. subHashCode64 = CalculateHashCode64(calledMethod.GenericArguments[1]);
  280. useCalculatedHashCode = CanComputeCompileTimeHash(calledMethod.GenericArguments[0]);
  281. useCalculatedSubHashCode = CanComputeCompileTimeHash(calledMethod.GenericArguments[1]);
  282. }
  283. #if DEBUG
  284. if (!useCalculatedHashCode && !useCalculatedSubHashCode)
  285. {
  286. throw new InvalidOperationException("Cannot replace when both hashes are invalid!");
  287. }
  288. #endif
  289. var methodToCall = "GetOrCreateUnsafe";
  290. TypeReference genericArgument = null;
  291. if (!useCalculatedHashCode)
  292. {
  293. methodToCall = "GetOrCreatePartiallyUnsafeWithSubHashCode";
  294. genericArgument = calledMethod.GenericArguments[0];
  295. }
  296. else if (!useCalculatedSubHashCode)
  297. {
  298. methodToCall = "GetOrCreatePartiallyUnsafeWithHashCode";
  299. genericArgument = calledMethod.GenericArguments[1];
  300. }
  301. var getOrCreateUnsafe = _assemblyDefinition.MainModule.ImportReference(
  302. calledMethod.DeclaringType.Resolve().Methods.First(m => m.Name == methodToCall));
  303. getOrCreateUnsafe.DeclaringType = calledMethod.DeclaringType;
  304. if (genericArgument != null)
  305. {
  306. var genericInstanceMethod = new GenericInstanceMethod(getOrCreateUnsafe);
  307. genericInstanceMethod.GenericArguments.Add(genericArgument);
  308. getOrCreateUnsafe = genericInstanceMethod;
  309. }
  310. var processor = method.Body.GetILProcessor();
  311. if (useCalculatedHashCode)
  312. {
  313. processor.InsertBefore(instruction, processor.Create(OpCodes.Ldc_I8, hashCode64));
  314. }
  315. if (useCalculatedSubHashCode)
  316. {
  317. processor.InsertBefore(instruction, processor.Create(OpCodes.Ldc_I8, subHashCode64));
  318. }
  319. processor.Replace(instruction, processor.Create(OpCodes.Call, getOrCreateUnsafe));
  320. }
  321. }
  322. finally
  323. {
  324. _instructionsToReplace.Clear();
  325. }
  326. }
  327. }
  328. }
  329. // WARNING: This **must** be kept in sync with the definition in BurstRuntime.cs!
  330. private static long HashStringWithFNV1A64(string text)
  331. {
  332. // Using http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-1a
  333. // with basis and prime:
  334. const ulong offsetBasis = 14695981039346656037;
  335. const ulong prime = 1099511628211;
  336. ulong result = offsetBasis;
  337. foreach(var c in text)
  338. {
  339. result = prime * (result ^ (byte)(c & 255));
  340. result = prime * (result ^ (byte)(c >> 8));
  341. }
  342. return (long)result;
  343. }
  344. private long CalculateHashCode64(TypeReference type)
  345. {
  346. try
  347. {
  348. #if DEBUG
  349. if (_builder.Length != 0)
  350. {
  351. throw new InvalidOperationException("StringBuilder wasn't cleared properly!");
  352. }
  353. #endif
  354. type.BuildAssemblyQualifiedName(_builder);
  355. return HashStringWithFNV1A64(_builder.ToString());
  356. }
  357. finally
  358. {
  359. _builder.Clear();
  360. }
  361. }
  362. private static bool TypeIsSharedStatic(TypeReference typeRef)
  363. {
  364. if (typeRef.Namespace != "Unity.Burst")
  365. {
  366. return false;
  367. }
  368. if (typeRef.Name != "SharedStatic`1")
  369. {
  370. return false;
  371. }
  372. return true;
  373. }
  374. private static bool TypeHasSharedStaticInIt(TypeDefinition typeDef)
  375. {
  376. foreach (var field in typeDef.Fields)
  377. {
  378. if (TypeIsSharedStatic(field.FieldType))
  379. {
  380. return true;
  381. }
  382. }
  383. return false;
  384. }
  385. private TypeDefinition InjectDelegate(TypeDefinition declaringType, string originalName, MethodDefinition managed, string uniqueSuffix)
  386. {
  387. var injectedDelegateType = new TypeDefinition(declaringType.Namespace, $"{originalName}{uniqueSuffix}{PostfixBurstDelegate}",
  388. TypeAttributes.NestedPublic |
  389. TypeAttributes.AutoLayout |
  390. TypeAttributes.AnsiClass |
  391. TypeAttributes.Sealed
  392. )
  393. {
  394. DeclaringType = declaringType,
  395. BaseType = _systemDelegateType
  396. };
  397. declaringType.NestedTypes.Add(injectedDelegateType);
  398. {
  399. var constructor = new MethodDefinition(".ctor",
  400. MethodAttributes.Public |
  401. MethodAttributes.HideBySig |
  402. MethodAttributes.SpecialName |
  403. MethodAttributes.RTSpecialName,
  404. _typeSystem.Void)
  405. {
  406. HasThis = true,
  407. IsManaged = true,
  408. IsRuntime = true,
  409. DeclaringType = injectedDelegateType
  410. };
  411. constructor.Parameters.Add(new ParameterDefinition(_typeSystem.Object));
  412. constructor.Parameters.Add(new ParameterDefinition(_typeSystem.IntPtr));
  413. injectedDelegateType.Methods.Add(constructor);
  414. }
  415. {
  416. var invoke = new MethodDefinition("Invoke",
  417. MethodAttributes.Public |
  418. MethodAttributes.HideBySig |
  419. MethodAttributes.NewSlot |
  420. MethodAttributes.Virtual,
  421. managed.ReturnType)
  422. {
  423. HasThis = true,
  424. IsManaged = true,
  425. IsRuntime = true,
  426. DeclaringType = injectedDelegateType
  427. };
  428. foreach (var parameter in managed.Parameters)
  429. {
  430. invoke.Parameters.Add(parameter);
  431. }
  432. injectedDelegateType.Methods.Add(invoke);
  433. }
  434. {
  435. var beginInvoke = new MethodDefinition("BeginInvoke",
  436. MethodAttributes.Public |
  437. MethodAttributes.HideBySig |
  438. MethodAttributes.NewSlot |
  439. MethodAttributes.Virtual,
  440. _systemIASyncResultType)
  441. {
  442. HasThis = true,
  443. IsManaged = true,
  444. IsRuntime = true,
  445. DeclaringType = injectedDelegateType
  446. };
  447. foreach (var parameter in managed.Parameters)
  448. {
  449. beginInvoke.Parameters.Add(parameter);
  450. }
  451. beginInvoke.Parameters.Add(new ParameterDefinition(_systemASyncCallbackType));
  452. beginInvoke.Parameters.Add(new ParameterDefinition(_typeSystem.Object));
  453. injectedDelegateType.Methods.Add(beginInvoke);
  454. }
  455. {
  456. var endInvoke = new MethodDefinition("EndInvoke",
  457. MethodAttributes.Public |
  458. MethodAttributes.HideBySig |
  459. MethodAttributes.NewSlot |
  460. MethodAttributes.Virtual,
  461. managed.ReturnType)
  462. {
  463. HasThis = true,
  464. IsManaged = true,
  465. IsRuntime = true,
  466. DeclaringType = injectedDelegateType
  467. };
  468. endInvoke.Parameters.Add(new ParameterDefinition(_systemIASyncResultType));
  469. injectedDelegateType.Methods.Add(endInvoke);
  470. }
  471. return injectedDelegateType;
  472. }
  473. private MethodDefinition CreateGetFunctionPointerDiscardMethod(TypeDefinition cls, FieldDefinition pointerField, FieldDefinition deferredCompilationField, MethodDefinition managedFallbackMethod, TypeDefinition injectedDelegate)
  474. {
  475. // Create GetFunctionPointer method:
  476. //
  477. // [BurstDiscard]
  478. // public static void GetFunctionPointerDiscard(ref IntPtr ptr) {
  479. // if (Pointer == null) {
  480. // Pointer = BurstCompiler.GetILPPMethodFunctionPointer2(DeferredCompilation, managedFallbackMethod, DelegateType);
  481. // }
  482. //
  483. // ptr = Pointer
  484. // }
  485. var getFunctionPointerDiscardMethod = new MethodDefinition(GetFunctionPointerDiscardName, MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, _typeSystem.Void)
  486. {
  487. ImplAttributes = MethodImplAttributes.IL | MethodImplAttributes.Managed,
  488. DeclaringType = cls
  489. };
  490. getFunctionPointerDiscardMethod.Parameters.Add(new ParameterDefinition(new ByReferenceType(_typeSystem.IntPtr)));
  491. var processor = getFunctionPointerDiscardMethod.Body.GetILProcessor();
  492. processor.Emit(OpCodes.Ldsfld, pointerField);
  493. var branchPosition = processor.Body.Instructions[processor.Body.Instructions.Count - 1];
  494. processor.Emit(OpCodes.Ldsfld, deferredCompilationField);
  495. processor.Emit(OpCodes.Ldtoken, managedFallbackMethod);
  496. processor.Emit(OpCodes.Ldtoken, injectedDelegate);
  497. processor.Emit(OpCodes.Call, _burstCompilerGetILPPMethodFunctionPointer);
  498. processor.Emit(OpCodes.Stsfld, pointerField);
  499. processor.Emit(OpCodes.Ldarg_0);
  500. processor.InsertAfter(branchPosition, Instruction.Create(OpCodes.Brtrue, processor.Body.Instructions[processor.Body.Instructions.Count - 1]));
  501. processor.Emit(OpCodes.Ldsfld, pointerField);
  502. processor.Emit(OpCodes.Stind_I);
  503. processor.Emit(OpCodes.Ret);
  504. cls.Methods.Add(FixDebugInformation(getFunctionPointerDiscardMethod));
  505. getFunctionPointerDiscardMethod.CustomAttributes.Add(new CustomAttribute(_burstDiscardAttributeConstructor));
  506. return getFunctionPointerDiscardMethod;
  507. }
  508. private MethodDefinition CreateGetFunctionPointerMethod(TypeDefinition cls, MethodDefinition getFunctionPointerDiscardMethod)
  509. {
  510. // Create GetFunctionPointer method:
  511. //
  512. // public static IntPtr GetFunctionPointer() {
  513. // var ptr;
  514. // GetFunctionPointerDiscard(ref ptr);
  515. // return ptr;
  516. // }
  517. var getFunctionPointerMethod = new MethodDefinition(GetFunctionPointerName, MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, _typeSystem.IntPtr)
  518. {
  519. ImplAttributes = MethodImplAttributes.IL | MethodImplAttributes.Managed,
  520. DeclaringType = cls
  521. };
  522. getFunctionPointerMethod.Body.Variables.Add(new VariableDefinition(_typeSystem.IntPtr));
  523. getFunctionPointerMethod.Body.InitLocals = true;
  524. var processor = getFunctionPointerMethod.Body.GetILProcessor();
  525. processor.Emit(OpCodes.Ldc_I4_0);
  526. processor.Emit(OpCodes.Conv_I);
  527. processor.Emit(OpCodes.Stloc_0);
  528. processor.Emit(OpCodes.Ldloca_S, (byte)0);
  529. processor.Emit(OpCodes.Call, getFunctionPointerDiscardMethod);
  530. processor.Emit(OpCodes.Ldloc_0);
  531. processor.Emit(OpCodes.Ret);
  532. cls.Methods.Add(FixDebugInformation(getFunctionPointerMethod));
  533. return getFunctionPointerMethod;
  534. }
  535. private void ProcessMethodForDirectCall(MethodDefinition burstCompileMethod)
  536. {
  537. var declaringType = burstCompileMethod.DeclaringType;
  538. var uniqueSuffix = $"_{burstCompileMethod.MetadataToken.RID:X8}";
  539. var injectedDelegate = InjectDelegate(declaringType, burstCompileMethod.Name, burstCompileMethod, uniqueSuffix);
  540. // Create a copy of the original method that will be the actual managed method
  541. // The original method is patched at the end of this method to call
  542. // the dispatcher that will go to the Burst implementation or the managed method (if in the editor and Burst is disabled)
  543. var managedFallbackMethod = new MethodDefinition($"{burstCompileMethod.Name}{PostfixManaged}", burstCompileMethod.Attributes, burstCompileMethod.ReturnType)
  544. {
  545. DeclaringType = declaringType,
  546. ImplAttributes = burstCompileMethod.ImplAttributes,
  547. MetadataToken = burstCompileMethod.MetadataToken,
  548. };
  549. // Ensure the CustomAttributes are the same
  550. managedFallbackMethod.CustomAttributes.Clear();
  551. foreach (var attr in burstCompileMethod.CustomAttributes)
  552. {
  553. managedFallbackMethod.CustomAttributes.Add(attr);
  554. }
  555. declaringType.Methods.Add(managedFallbackMethod);
  556. foreach (var parameter in burstCompileMethod.Parameters)
  557. {
  558. managedFallbackMethod.Parameters.Add(parameter);
  559. }
  560. // Copy the body from the original burst method to the managed fallback, we'll replace the burstCompileMethod body later.
  561. managedFallbackMethod.Body.InitLocals = burstCompileMethod.Body.InitLocals;
  562. managedFallbackMethod.Body.LocalVarToken = burstCompileMethod.Body.LocalVarToken;
  563. managedFallbackMethod.Body.MaxStackSize = burstCompileMethod.Body.MaxStackSize;
  564. foreach (var variable in burstCompileMethod.Body.Variables)
  565. {
  566. managedFallbackMethod.Body.Variables.Add(variable);
  567. }
  568. foreach (var instruction in burstCompileMethod.Body.Instructions)
  569. {
  570. managedFallbackMethod.Body.Instructions.Add(instruction);
  571. }
  572. foreach (var exceptionHandler in burstCompileMethod.Body.ExceptionHandlers)
  573. {
  574. managedFallbackMethod.Body.ExceptionHandlers.Add(exceptionHandler);
  575. }
  576. managedFallbackMethod.ImplAttributes &= MethodImplAttributes.NoInlining;
  577. // 0x0100 is AggressiveInlining
  578. managedFallbackMethod.ImplAttributes |= (MethodImplAttributes)0x0100;
  579. // The method needs to be public because we query for it in the ILPP code.
  580. managedFallbackMethod.Attributes &= ~MethodAttributes.Private;
  581. managedFallbackMethod.Attributes |= MethodAttributes.Public;
  582. // private static class (Name_RID.$Postfix)
  583. var cls = new TypeDefinition(declaringType.Namespace, $"{burstCompileMethod.Name}{uniqueSuffix}{PostfixBurstDirectCall}",
  584. TypeAttributes.NestedAssembly |
  585. TypeAttributes.AutoLayout |
  586. TypeAttributes.AnsiClass |
  587. TypeAttributes.Abstract |
  588. TypeAttributes.Sealed |
  589. TypeAttributes.BeforeFieldInit
  590. )
  591. {
  592. DeclaringType = declaringType,
  593. BaseType = _typeSystem.Object
  594. };
  595. declaringType.NestedTypes.Add(cls);
  596. // Create Field:
  597. //
  598. // private static IntPtr Pointer;
  599. var pointerField = new FieldDefinition("Pointer", FieldAttributes.Static | FieldAttributes.Private, _typeSystem.IntPtr)
  600. {
  601. DeclaringType = cls
  602. };
  603. cls.Fields.Add(pointerField);
  604. // Create Field:
  605. //
  606. // private static IntPtr DeferredCompilation;
  607. var deferredCompilationField = new FieldDefinition("DeferredCompilation", FieldAttributes.Static | FieldAttributes.Private, _typeSystem.IntPtr)
  608. {
  609. DeclaringType = cls
  610. };
  611. cls.Fields.Add(deferredCompilationField);
  612. var getFunctionPointerDiscardMethod = CreateGetFunctionPointerDiscardMethod(cls, pointerField, deferredCompilationField, managedFallbackMethod, injectedDelegate);
  613. var getFunctionPointerMethod = CreateGetFunctionPointerMethod(cls, getFunctionPointerDiscardMethod);
  614. var asmAttribute = new CustomAttribute(_burstCompilerCompileUnsafeStaticMethodReinitialiseAttributeCtor);
  615. asmAttribute.ConstructorArguments.Add(new CustomAttributeArgument(_systemType, cls));
  616. _assemblyDefinition.CustomAttributes.Add(asmAttribute);
  617. // Create the static Constructor Method (called via .cctor and via reflection on burst compilation enable)
  618. // private static void Constructor() {
  619. // deferredCompilation = CompileILPPMethod2(burstCompileMethod);
  620. // }
  621. var constructor = new MethodDefinition("Constructor", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static, _typeSystem.Void)
  622. {
  623. ImplAttributes = MethodImplAttributes.IL | MethodImplAttributes.Managed,
  624. DeclaringType = cls
  625. };
  626. var processor = constructor.Body.GetILProcessor();
  627. // In the editor we'll ask for the fallback method, it will be effectively redirected to the burstCompileMethod
  628. // While in the player managedFallbackMethod won't be in the compiled delegate cache, but burstCompileMethod
  629. // will, and it's safe to use the burstCompileMethod because it will always be the Burst compiled one
  630. processor.Emit(OpCodes.Ldtoken, IsForEditor ? managedFallbackMethod : burstCompileMethod);
  631. processor.Emit(OpCodes.Call, _burstCompilerCompileILPPMethod);
  632. processor.Emit(OpCodes.Stsfld, deferredCompilationField);
  633. processor.Emit(OpCodes.Ret);
  634. cls.Methods.Add(FixDebugInformation(constructor));
  635. // Create an Initialize method
  636. // This will be called from the single [RuntimeInitializeOnLoadMethod]
  637. // method that we'll generate for this assembly.
  638. // Its only job is to cause the .cctor to run.
  639. //
  640. // public static void Initialize() { }
  641. var initializeMethod = new MethodDefinition("Initialize", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static, _typeSystem.Void)
  642. {
  643. ImplAttributes = MethodImplAttributes.IL | MethodImplAttributes.Managed,
  644. DeclaringType = cls
  645. };
  646. processor = initializeMethod.Body.GetILProcessor();
  647. processor.Emit(OpCodes.Ret);
  648. cls.Methods.Add(FixDebugInformation(initializeMethod));
  649. var currentInitializer = initializeMethod;
  650. var currentDeclaringType = declaringType;
  651. // If our target method is hidden behind one or more nested private classes, then
  652. // create a method on the parent type that calls said method (for each private nested class)
  653. while (currentDeclaringType.DeclaringType != null)
  654. {
  655. var parentType = currentDeclaringType.DeclaringType;
  656. if (((currentDeclaringType.Attributes & TypeAttributes.NestedPrivate) == TypeAttributes.NestedPrivate) ||
  657. ((currentDeclaringType.Attributes & TypeAttributes.NestedFamily) == TypeAttributes.NestedFamily))
  658. {
  659. var redirectingInitializer = new MethodDefinition($"Initialize${declaringType.Name}_{cls.Name}",
  660. MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static,
  661. _typeSystem.Void)
  662. {
  663. ImplAttributes = MethodImplAttributes.IL | MethodImplAttributes.Managed,
  664. DeclaringType = parentType
  665. };
  666. processor = redirectingInitializer.Body.GetILProcessor();
  667. processor.Emit(OpCodes.Call, currentInitializer);
  668. processor.Emit(OpCodes.Ret);
  669. parentType.Methods.Add(redirectingInitializer);
  670. currentInitializer = redirectingInitializer;
  671. }
  672. currentDeclaringType = parentType;
  673. }
  674. _directCallInitializeMethods.Add(currentInitializer);
  675. // Create the static constructor
  676. //
  677. // public static .cctor() {
  678. // Constructor();
  679. // }
  680. var cctor = new MethodDefinition(".cctor", MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Static, _typeSystem.Void)
  681. {
  682. ImplAttributes = MethodImplAttributes.IL | MethodImplAttributes.Managed,
  683. DeclaringType = cls,
  684. };
  685. processor = cctor.Body.GetILProcessor();
  686. processor.Emit(OpCodes.Call, constructor);
  687. processor.Emit(OpCodes.Ret);
  688. cls.Methods.Add(FixDebugInformation(cctor));
  689. // Create the Invoke method based on the original method (same signature)
  690. //
  691. // public static XXX Invoke(...args) {
  692. // if (BurstCompiler.IsEnabled)
  693. // {
  694. // var funcPtr = GetFunctionPointer();
  695. // if (funcPtr != null) return funcPtr(...args);
  696. // }
  697. // return OriginalMethod(...args);
  698. // }
  699. var invokeAttributes = managedFallbackMethod.Attributes;
  700. invokeAttributes &= ~MethodAttributes.Private;
  701. invokeAttributes |= MethodAttributes.Public;
  702. var invoke = new MethodDefinition(InvokeName, invokeAttributes, burstCompileMethod.ReturnType)
  703. {
  704. ImplAttributes = MethodImplAttributes.IL | MethodImplAttributes.Managed,
  705. DeclaringType = cls
  706. };
  707. var signature = new CallSite(burstCompileMethod.ReturnType)
  708. {
  709. CallingConvention = MethodCallingConvention.C
  710. };
  711. foreach (var parameter in burstCompileMethod.Parameters)
  712. {
  713. invoke.Parameters.Add(parameter);
  714. signature.Parameters.Add(parameter);
  715. }
  716. invoke.Body.Variables.Add(new VariableDefinition(_typeSystem.IntPtr));
  717. invoke.Body.InitLocals = true;
  718. processor = invoke.Body.GetILProcessor();
  719. processor.Emit(OpCodes.Call, _burstCompilerIsEnabledMethodDefinition);
  720. var branchPosition0 = processor.Body.Instructions[processor.Body.Instructions.Count - 1];
  721. processor.Emit(OpCodes.Call, getFunctionPointerMethod);
  722. processor.Emit(OpCodes.Stloc_0);
  723. processor.Emit(OpCodes.Ldloc_0);
  724. var branchPosition1 = processor.Body.Instructions[processor.Body.Instructions.Count - 1];
  725. EmitArguments(processor, invoke);
  726. processor.Emit(OpCodes.Ldloc_0);
  727. processor.Emit(OpCodes.Calli, signature);
  728. processor.Emit(OpCodes.Ret);
  729. var previousRet = processor.Body.Instructions[processor.Body.Instructions.Count - 1];
  730. EmitArguments(processor, invoke);
  731. processor.Emit(OpCodes.Call, managedFallbackMethod);
  732. processor.Emit(OpCodes.Ret);
  733. // Insert the branch once we have emitted the instructions
  734. processor.InsertAfter(branchPosition0, Instruction.Create(OpCodes.Brfalse, previousRet.Next));
  735. processor.InsertAfter(branchPosition1, Instruction.Create(OpCodes.Brfalse, previousRet.Next));
  736. cls.Methods.Add(FixDebugInformation(invoke));
  737. // Final patching of the original method
  738. // public static XXX OriginalMethod(...args) {
  739. // Name_RID.$Postfix.Invoke(...args);
  740. // ret;
  741. // }
  742. burstCompileMethod.Body = new MethodBody(burstCompileMethod);
  743. processor = burstCompileMethod.Body.GetILProcessor();
  744. EmitArguments(processor, burstCompileMethod);
  745. processor.Emit(OpCodes.Call, invoke);
  746. processor.Emit(OpCodes.Ret);
  747. FixDebugInformation(burstCompileMethod);
  748. }
  749. private static MethodDefinition FixDebugInformation(MethodDefinition method)
  750. {
  751. method.DebugInformation.Scope = new ScopeDebugInformation(method.Body.Instructions.First(), method.Body.Instructions.Last());
  752. return method;
  753. }
  754. private AssemblyDefinition GetAsmDefinitionFromFile(AssemblyResolver loader, string assemblyName)
  755. {
  756. if (loader.TryResolve(AssemblyNameReference.Parse(assemblyName), out var result))
  757. {
  758. return result;
  759. }
  760. return null;
  761. }
  762. private MethodReference _unityEngineInitializeOnLoadAttributeCtor;
  763. private TypeReference _unityEngineRuntimeInitializeLoadType;
  764. private FieldDefinition _unityEngineRuntimeInitializeLoadAfterAssemblies;
  765. private MethodReference _unityEditorInitilizeOnLoadAttributeCtor;
  766. private void InitializeBurstAssembly(AssemblyDefinition burstAssembly)
  767. {
  768. _burstAssembly = burstAssembly;
  769. _burstCompilerTypeDefinition = burstAssembly.MainModule.GetType("Unity.Burst", "BurstCompiler");
  770. _burstCompilerIsEnabledMethodDefinition = _assemblyDefinition.MainModule.ImportReference(_burstCompilerTypeDefinition.Methods.FirstOrDefault(x => x.Name == "get_IsEnabled"));
  771. _burstCompilerCompileILPPMethod = _assemblyDefinition.MainModule.ImportReference(_burstCompilerTypeDefinition.Methods.FirstOrDefault(x => x.Name == "CompileILPPMethod2"));
  772. _burstCompilerGetILPPMethodFunctionPointer = _assemblyDefinition.MainModule.ImportReference(_burstCompilerTypeDefinition.Methods.FirstOrDefault(x => x.Name == "GetILPPMethodFunctionPointer2"));
  773. var reinitializeAttribute = _burstCompilerTypeDefinition.NestedTypes.FirstOrDefault(x => x.Name == "StaticTypeReinitAttribute");
  774. _burstCompilerCompileUnsafeStaticMethodReinitialiseAttributeCtor = _assemblyDefinition.MainModule.ImportReference(reinitializeAttribute.Methods.FirstOrDefault(x=>x.Name == ".ctor" && x.HasParameters));
  775. var corLibrary = Loader.Resolve((AssemblyNameReference)_typeSystem.CoreLibrary);
  776. _systemType = _assemblyDefinition.MainModule.ImportReference(corLibrary.MainModule.GetType("System.Type"));
  777. _systemDelegateType = _assemblyDefinition.MainModule.ImportReference(corLibrary.MainModule.GetType("System.MulticastDelegate"));
  778. _systemASyncCallbackType = _assemblyDefinition.MainModule.ImportReference(corLibrary.MainModule.GetType("System.AsyncCallback"));
  779. _systemIASyncResultType = _assemblyDefinition.MainModule.ImportReference(corLibrary.MainModule.GetType("System.IAsyncResult"));
  780. var asmDef = GetAsmDefinitionFromFile(Loader, "UnityEngine.CoreModule");
  781. var runtimeInitializeOnLoadMethodAttribute = asmDef.MainModule.GetType("UnityEngine", "RuntimeInitializeOnLoadMethodAttribute");
  782. var runtimeInitializeLoadType = asmDef.MainModule.GetType("UnityEngine", "RuntimeInitializeLoadType");
  783. var burstDiscardType = asmDef.MainModule.GetType("Unity.Burst", "BurstDiscardAttribute");
  784. _burstDiscardAttributeConstructor = _assemblyDefinition.MainModule.ImportReference(burstDiscardType.Methods.First(method => method.Name == ".ctor"));
  785. _unityEngineInitializeOnLoadAttributeCtor = _assemblyDefinition.MainModule.ImportReference(runtimeInitializeOnLoadMethodAttribute.Methods.FirstOrDefault(x => x.Name == ".ctor" && x.HasParameters));
  786. _unityEngineRuntimeInitializeLoadType = _assemblyDefinition.MainModule.ImportReference(runtimeInitializeLoadType);
  787. _unityEngineRuntimeInitializeLoadAfterAssemblies = runtimeInitializeLoadType.Fields.FirstOrDefault(x => x.Name=="AfterAssembliesLoaded");
  788. if (IsForEditor && !_skipInitializeOnLoad)
  789. {
  790. asmDef = GetAsmDefinitionFromFile(Loader, "UnityEditor.CoreModule");
  791. if (asmDef == null)
  792. asmDef = GetAsmDefinitionFromFile(Loader, "UnityEditor");
  793. var initializeOnLoadMethodAttribute = asmDef.MainModule.GetType("UnityEditor", "InitializeOnLoadMethodAttribute");
  794. _unityEditorInitilizeOnLoadAttributeCtor = _assemblyDefinition.MainModule.ImportReference(initializeOnLoadMethodAttribute.Methods.FirstOrDefault(x => x.Name == ".ctor" && !x.HasParameters));
  795. }
  796. }
  797. private static void EmitArguments(ILProcessor processor, MethodDefinition method)
  798. {
  799. for (var i = 0; i < method.Parameters.Count; i++)
  800. {
  801. switch (i)
  802. {
  803. case 0:
  804. processor.Emit(OpCodes.Ldarg_0);
  805. break;
  806. case 1:
  807. processor.Emit(OpCodes.Ldarg_1);
  808. break;
  809. case 2:
  810. processor.Emit(OpCodes.Ldarg_2);
  811. break;
  812. case 3:
  813. processor.Emit(OpCodes.Ldarg_3);
  814. break;
  815. default:
  816. if (i <= 255)
  817. {
  818. processor.Emit(OpCodes.Ldarg_S, (byte)i);
  819. }
  820. else
  821. {
  822. processor.Emit(OpCodes.Ldarg, i);
  823. }
  824. break;
  825. }
  826. }
  827. }
  828. private static bool TryGetBurstCompilerAttribute(ICustomAttributeProvider provider, out CustomAttribute customAttribute)
  829. {
  830. if (provider.HasCustomAttributes)
  831. {
  832. foreach (var customAttr in provider.CustomAttributes)
  833. {
  834. if (customAttr.Constructor.DeclaringType.Name == "BurstCompileAttribute")
  835. {
  836. customAttribute = customAttr;
  837. return true;
  838. }
  839. }
  840. }
  841. customAttribute = null;
  842. return false;
  843. }
  844. }
  845. }