A game about forced loneliness, made by TACStudios
1using System;
2using System.Diagnostics;
3using System.IO;
4using System.Reflection;
5using System.Runtime.InteropServices;
6using System.Collections.Generic;
7using UnityEngine.Scripting;
8using System.Linq;
9using System.Text;
10
11namespace Unity.Burst
12{
13 /// <summary>
14 /// The burst compiler runtime frontend.
15 /// </summary>
16 ///
17 public static class BurstCompiler
18 {
19 /// <summary>
20 /// Check if the LoadAdditionalLibrary API is supported by the current version of Unity
21 /// </summary>
22 /// <returns>True if the LoadAdditionalLibrary API can be used by the current version of Unity</returns>
23 public static bool IsLoadAdditionalLibrarySupported()
24 {
25 return IsApiAvailable("LoadBurstLibrary");
26 }
27
28#if UNITY_EDITOR
29 static unsafe BurstCompiler()
30 {
31 // Store pointers to Log and Compile callback methods.
32 // For more info about why we need to do this, see comments in CallbackStubManager.
33 string GetFunctionPointer<TDelegate>(TDelegate callback)
34 {
35 GCHandle.Alloc(callback); // Ensure delegate is never garbage-collected.
36 var callbackFunctionPointer = Marshal.GetFunctionPointerForDelegate(callback);
37 return "0x" + callbackFunctionPointer.ToInt64().ToString("X16");
38 }
39
40 EagerCompileLogCallbackFunctionPointer = GetFunctionPointer<LogCallbackDelegate>(EagerCompileLogCallback);
41 ManagedResolverFunctionPointer = GetFunctionPointer<ManagedFnPtrResolverDelegate>(ManagedResolverFunction);
42 ProgressCallbackFunctionPointer = GetFunctionPointer<ProgressCallbackDelegate>(ProgressCallback);
43 ProfileBeginCallbackFunctionPointer = GetFunctionPointer<ProfileBeginCallbackDelegate>(ProfileBeginCallback);
44 ProfileEndCallbackFunctionPointer = GetFunctionPointer<ProfileEndCallbackDelegate>(ProfileEndCallback);
45 }
46#endif
47
48 private class CommandBuilder
49 {
50 private StringBuilder _builder;
51 private bool _hasArgs;
52
53 public CommandBuilder()
54 {
55 _builder = new StringBuilder();
56 _hasArgs = false;
57 }
58
59 public CommandBuilder Begin(string cmd)
60 {
61 _builder.Clear();
62 _hasArgs = false;
63 _builder.Append(cmd);
64 return this;
65 }
66
67 public CommandBuilder With(string arg)
68 {
69 if (!_hasArgs) _builder.Append(' ');
70 _hasArgs = true;
71 _builder.Append(arg);
72 return this;
73 }
74
75 public CommandBuilder With(IntPtr arg)
76 {
77 if (!_hasArgs) _builder.Append(' ');
78 _hasArgs = true;
79 _builder.AppendFormat("0x{0:X16}", arg.ToInt64());
80 return this;
81 }
82
83 public CommandBuilder And(char sep = '|')
84 {
85 _builder.Append(sep);
86 return this;
87 }
88
89 public string SendToCompiler()
90 {
91 return SendRawCommandToCompiler(_builder.ToString());
92 }
93 }
94
95 [ThreadStatic]
96 private static CommandBuilder _cmdBuilder;
97
98 private static CommandBuilder BeginCompilerCommand(string cmd)
99 {
100 if (_cmdBuilder == null)
101 {
102 _cmdBuilder = new CommandBuilder();
103 }
104
105 return _cmdBuilder.Begin(cmd);
106 }
107
108#if BURST_INTERNAL
109 [ThreadStatic]
110 public static Func<object, IntPtr> InternalCompiler;
111#endif
112
113 /// <summary>
114 /// Internal variable setup by BurstCompilerOptions.
115 /// </summary>
116#if BURST_INTERNAL
117
118 [ThreadStatic] // As we are changing this boolean via BurstCompilerOptions in btests and we are running multithread tests
119 // we would change a global and it would generate random errors, so specifically for btests, we are using a TLS.
120 public
121#else
122 internal
123#endif
124 static bool _IsEnabled;
125
126 /// <summary>
127 /// Gets a value indicating whether Burst is enabled.
128 /// </summary>
129#if UNITY_EDITOR || BURST_INTERNAL
130 public static bool IsEnabled => _IsEnabled;
131#else
132 public static bool IsEnabled => _IsEnabled && BurstCompilerHelper.IsBurstGenerated;
133#endif
134
135 /// <summary>
136 /// Gets the global options for the burst compiler.
137 /// </summary>
138 public static readonly BurstCompilerOptions Options = new BurstCompilerOptions(true);
139
140 /// <summary>
141 /// Sets the execution mode for all jobs spawned from now on.
142 /// </summary>
143 /// <param name="mode">Specifiy the required execution mode</param>
144 public static void SetExecutionMode(BurstExecutionEnvironment mode)
145 {
146 Burst.LowLevel.BurstCompilerService.SetCurrentExecutionMode((uint)mode);
147 }
148 /// <summary>
149 /// Retrieve the current execution mode that is configured.
150 /// </summary>
151 /// <returns>Currently configured execution mode</returns>
152 public static BurstExecutionEnvironment GetExecutionMode()
153 {
154 return (BurstExecutionEnvironment)Burst.LowLevel.BurstCompilerService.GetCurrentExecutionMode();
155 }
156
157 /// <summary>
158 /// Compile the following delegate with burst and return a new delegate.
159 /// </summary>
160 /// <typeparam name="T"></typeparam>
161 /// <param name="delegateMethod"></param>
162 /// <returns></returns>
163 /// <remarks>NOT AVAILABLE, unsafe to use</remarks>
164 internal static unsafe T CompileDelegate<T>(T delegateMethod) where T : class
165 {
166 // We have added support for runtime CompileDelegate in 2018.2+
167 void* function = Compile(delegateMethod, false);
168 object res = System.Runtime.InteropServices.Marshal.GetDelegateForFunctionPointer((IntPtr)function, delegateMethod.GetType());
169 return (T)res;
170 }
171
172 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
173 private static void VerifyDelegateIsNotMulticast<T>(T delegateMethod) where T : class
174 {
175 var delegateKind = delegateMethod as Delegate;
176 if (delegateKind.GetInvocationList().Length > 1)
177 {
178 throw new InvalidOperationException($"Burst does not support multicast delegates, please use a regular delegate for `{delegateMethod}'");
179 }
180 }
181
182 [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
183 private static void VerifyDelegateHasCorrectUnmanagedFunctionPointerAttribute<T>(T delegateMethod) where T : class
184 {
185 var attrib = delegateMethod.GetType().GetCustomAttribute<System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute>();
186 if (attrib == null || attrib.CallingConvention != CallingConvention.Cdecl)
187 {
188#if !BURST_INTERNAL
189 UnityEngine.Debug.LogWarning($"The delegate type {delegateMethod.GetType().FullName} should be decorated with [UnmanagedFunctionPointer(CallingConvention.Cdecl)] to ensure runtime interoperabilty between managed code and Burst-compiled code.");
190#endif
191 }
192 }
193
194 /// <summary>
195 /// DO NOT USE - deprecated.
196 /// </summary>
197 /// <param name="burstMethodHandle">The Burst method to compile.</param>
198 /// <param name="managedMethodHandle">The fallback managed method to use.</param>
199 /// <param name="delegateTypeHandle">The type of the delegate used to execute these methods.</param>
200 /// <returns>Nothing</returns>
201 [Obsolete("This method will be removed in a future version of Burst")]
202 public static unsafe IntPtr CompileILPPMethod(RuntimeMethodHandle burstMethodHandle, RuntimeMethodHandle managedMethodHandle, RuntimeTypeHandle delegateTypeHandle)
203 {
204 throw new NotImplementedException();
205 }
206
207 /// <summary>
208 /// Compile an IL Post-Processed method.
209 /// </summary>
210 /// <param name="burstMethodHandle">The Burst method to compile.</param>
211 /// <returns>A token that must be passed to <see cref="GetILPPMethodFunctionPointer2"/> to get an actual executable function pointer.</returns>
212 public static unsafe IntPtr CompileILPPMethod2(RuntimeMethodHandle burstMethodHandle)
213 {
214 if (burstMethodHandle.Value == IntPtr.Zero)
215 {
216 throw new ArgumentNullException(nameof(burstMethodHandle));
217 }
218
219 OnCompileILPPMethod2?.Invoke();
220
221 var burstMethod = (MethodInfo)MethodBase.GetMethodFromHandle(burstMethodHandle);
222
223 return (IntPtr)Compile(new FakeDelegate(burstMethod), burstMethod, isFunctionPointer: true, isILPostProcessing: true);
224 }
225
226 internal static Action OnCompileILPPMethod2;
227
228 /// <summary>
229 /// DO NOT USE - deprecated.
230 /// </summary>
231 /// <param name="ilppMethod">The result of a previous call to <see cref="CompileILPPMethod"/>.</param>
232 /// <returns>Nothing.</returns>
233 [Obsolete("This method will be removed in a future version of Burst")]
234 public static unsafe void* GetILPPMethodFunctionPointer(IntPtr ilppMethod)
235 {
236 throw new NotImplementedException();
237 }
238
239 /// <summary>
240 /// For a previous call to <see cref="CompileILPPMethod2"/>, get the actual executable function pointer.
241 /// </summary>
242 /// <param name="ilppMethod">The result of a previous call to <see cref="CompileILPPMethod"/>.</param>
243 /// <param name="managedMethodHandle">The fallback managed method to use.</param>
244 /// <param name="delegateTypeHandle">The type of the delegate used to execute these methods.</param>
245 /// <returns>A pointer into an executable region, for running the function pointer.</returns>
246 public static unsafe void* GetILPPMethodFunctionPointer2(IntPtr ilppMethod, RuntimeMethodHandle managedMethodHandle, RuntimeTypeHandle delegateTypeHandle)
247 {
248 if (ilppMethod == IntPtr.Zero)
249 {
250 throw new ArgumentNullException(nameof(ilppMethod));
251 }
252
253 if (managedMethodHandle.Value == IntPtr.Zero)
254 {
255 throw new ArgumentNullException(nameof(managedMethodHandle));
256 }
257
258 if (delegateTypeHandle.Value == IntPtr.Zero)
259 {
260 throw new ArgumentNullException(nameof(delegateTypeHandle));
261 }
262
263 // If we are in the editor, we need to route a command to the compiler to start compiling the deferred ILPP compilation.
264 // Otherwise if we're in Burst's internal testing, or in a player build, we already actually have the actual executable
265 // pointer address, and we just return that.
266#if UNITY_EDITOR
267 var managedMethod = (MethodInfo)MethodBase.GetMethodFromHandle(managedMethodHandle);
268 var delegateType = Type.GetTypeFromHandle(delegateTypeHandle);
269 var managedFallbackDelegate = Delegate.CreateDelegate(delegateType, managedMethod);
270
271 var handle = GCHandle.Alloc(managedFallbackDelegate);
272
273 var result =
274 BeginCompilerCommand(BurstCompilerOptions.CompilerCommandILPPCompilation)
275 .With(ilppMethod).And()
276 .With(ManagedResolverFunctionPointer).And()
277 .With(GCHandle.ToIntPtr(handle))
278 .SendToCompiler();
279
280 return new IntPtr(Convert.ToInt64(result, 16)).ToPointer();
281#else
282 return ilppMethod.ToPointer();
283#endif
284 }
285
286 /// <summary>
287 /// DO NOT USE - deprecated.
288 /// </summary>
289 /// <param name="handle">A runtime method handle.</param>
290 /// <returns>Nothing.</returns>
291 [Obsolete("This method will be removed in a future version of Burst")]
292 public static unsafe void* CompileUnsafeStaticMethod(RuntimeMethodHandle handle)
293 {
294 throw new NotImplementedException();
295 }
296
297 /// <summary>
298 /// Compile the following delegate into a function pointer with burst, invokable from a Burst Job or from regular C#.
299 /// </summary>
300 /// <typeparam name="T">Type of the delegate of the function pointer</typeparam>
301 /// <param name="delegateMethod">The delegate to compile</param>
302 /// <returns>A function pointer invokable from a Burst Job or from regular C#</returns>
303 public static unsafe FunctionPointer<T> CompileFunctionPointer<T>(T delegateMethod) where T : class
304 {
305 VerifyDelegateIsNotMulticast<T>(delegateMethod);
306 VerifyDelegateHasCorrectUnmanagedFunctionPointerAttribute<T>(delegateMethod);
307 // We have added support for runtime CompileDelegate in 2018.2+
308 void* function = Compile(delegateMethod, true);
309 return new FunctionPointer<T>(new IntPtr(function));
310 }
311
312 [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
313 internal class StaticTypeReinitAttribute : Attribute
314 {
315 public readonly Type reinitType;
316
317 public StaticTypeReinitAttribute(Type toReinit)
318 {
319 reinitType = toReinit;
320 }
321 }
322
323 private static unsafe void* Compile(object delegateObj, bool isFunctionPointer)
324 {
325 if (!(delegateObj is Delegate)) throw new ArgumentException("object instance must be a System.Delegate", nameof(delegateObj));
326 var delegateMethod = (Delegate)delegateObj;
327 return Compile(delegateMethod, delegateMethod.Method, isFunctionPointer, false);
328 }
329
330 private static unsafe void* Compile(object delegateObj, MethodInfo methodInfo, bool isFunctionPointer, bool isILPostProcessing)
331 {
332 if (delegateObj == null) throw new ArgumentNullException(nameof(delegateObj));
333
334 if (delegateObj.GetType().IsGenericType)
335 {
336 throw new InvalidOperationException($"The delegate type `{delegateObj.GetType()}` must be a non-generic type");
337 }
338 if (!methodInfo.IsStatic)
339 {
340 throw new InvalidOperationException($"The method `{methodInfo}` must be static. Instance methods are not supported");
341 }
342 if (methodInfo.IsGenericMethod)
343 {
344 throw new InvalidOperationException($"The method `{methodInfo}` must be a non-generic method");
345 }
346
347#if ENABLE_IL2CPP
348 if (isFunctionPointer && !isILPostProcessing &&
349 methodInfo.GetCustomAttributes().All(s => s.GetType().Name != "MonoPInvokeCallbackAttribute"))
350 {
351 UnityEngine.Debug.Log($"The method `{methodInfo}` must have `MonoPInvokeCallback` attribute to be compatible with IL2CPP!");
352 }
353#endif
354
355 void* function;
356
357#if BURST_INTERNAL
358 // Internally in Burst tests, we callback the C# method instead
359 function = (void*)InternalCompiler(delegateObj);
360#else
361
362 Delegate managedFallbackDelegateMethod = null;
363
364 if (!isILPostProcessing)
365 {
366 managedFallbackDelegateMethod = delegateObj as Delegate;
367 }
368
369 var delegateMethod = delegateObj as Delegate;
370
371#if UNITY_EDITOR
372 string defaultOptions;
373
374 // In case Burst is disabled entirely from the command line
375 if (BurstCompilerOptions.ForceDisableBurstCompilation)
376 {
377 if (isILPostProcessing)
378 {
379 return null;
380 }
381 else
382 {
383 GCHandle.Alloc(managedFallbackDelegateMethod);
384 function = (void*)Marshal.GetFunctionPointerForDelegate(managedFallbackDelegateMethod);
385 return function;
386 }
387 }
388
389 if (isILPostProcessing)
390 {
391 defaultOptions = "--" + BurstCompilerOptions.OptionJitIsForFunctionPointer + "\n";
392 }
393 else if (isFunctionPointer)
394 {
395 defaultOptions = "--" + BurstCompilerOptions.OptionJitIsForFunctionPointer + "\n";
396 // Make sure that the delegate will never be collected
397 var delHandle = GCHandle.Alloc(managedFallbackDelegateMethod);
398 defaultOptions += "--" + BurstCompilerOptions.OptionJitManagedDelegateHandle + "0x" + ManagedResolverFunctionPointer + "|" + "0x" + GCHandle.ToIntPtr(delHandle).ToInt64().ToString("X16");
399 }
400 else
401 {
402 defaultOptions = "--" + BurstCompilerOptions.OptionJitEnableSynchronousCompilation;
403 }
404
405 string extraOptions;
406 // The attribute is directly on the method, so we recover the underlying method here
407 if (Options.TryGetOptions(methodInfo, out extraOptions, isForILPostProcessing: isILPostProcessing))
408 {
409 if (!string.IsNullOrWhiteSpace(extraOptions))
410 {
411 defaultOptions += "\n" + extraOptions;
412 }
413
414 var delegateMethodId = Unity.Burst.LowLevel.BurstCompilerService.CompileAsyncDelegateMethod(delegateObj, defaultOptions);
415 function = Unity.Burst.LowLevel.BurstCompilerService.GetAsyncCompiledAsyncDelegateMethod(delegateMethodId);
416 }
417#else
418 // The attribute is directly on the method, so we recover the underlying method here
419 if (BurstCompilerOptions.HasBurstCompileAttribute(methodInfo))
420 {
421 if (Options.EnableBurstCompilation && BurstCompilerHelper.IsBurstGenerated)
422 {
423 var delegateMethodId = Unity.Burst.LowLevel.BurstCompilerService.CompileAsyncDelegateMethod(delegateObj, string.Empty);
424 function = Unity.Burst.LowLevel.BurstCompilerService.GetAsyncCompiledAsyncDelegateMethod(delegateMethodId);
425 }
426 else
427 {
428 // If this is for direct-call, and we're in a player, with Burst disabled, then we should return null,
429 // since we don't actually have a managedFallbackDelegateMethod at this point.
430 if (isILPostProcessing)
431 {
432 return null;
433 }
434
435 // Make sure that the delegate will never be collected
436 GCHandle.Alloc(managedFallbackDelegateMethod);
437 // If we are in a standalone player, and burst is disabled and we are actually
438 // trying to load a function pointer, in that case we need to support it
439 // so we are then going to use the managed function directly
440 // NOTE: When running under IL2CPP, this could lead to a `System.NotSupportedException : To marshal a managed method, please add an attribute named 'MonoPInvokeCallback' to the method definition.`
441 // so in that case, the method needs to have `MonoPInvokeCallback`
442 // but that's a requirement for IL2CPP, not an issue with burst
443 function = (void*)Marshal.GetFunctionPointerForDelegate(managedFallbackDelegateMethod);
444 }
445 }
446#endif
447 else
448 {
449 throw new InvalidOperationException($"Burst cannot compile the function pointer `{methodInfo}` because the `[BurstCompile]` attribute is missing");
450 }
451#endif
452 // Should not happen but in that case, we are still trying to generated an error
453 // It can be null if we are trying to compile a function in a standalone player
454 // and the function was not compiled. In that case, we need to output an error
455 if (function == null)
456 {
457 throw new InvalidOperationException($"Burst failed to compile the function pointer `{methodInfo}`");
458 }
459
460 // When burst compilation is disabled, we are still returning a valid stub function pointer (the a pointer to the managed function)
461 // so that CompileFunctionPointer actually returns a delegate in all cases
462 return function;
463 }
464
465 /// <summary>
466 /// Lets the compiler service know we are shutting down, called by the event on OnDomainUnload, if EditorApplication.quitting was called
467 /// </summary>
468 internal static void Shutdown()
469 {
470#if UNITY_EDITOR
471 SendCommandToCompiler(BurstCompilerOptions.CompilerCommandShutdown);
472#endif
473 }
474
475#if UNITY_EDITOR
476 internal static void SetDefaultOptions()
477 {
478 SendCommandToCompiler(BurstCompilerOptions.CompilerCommandSetDefaultOptions, Options.GetOptions(isForCompilerClient: true));
479 }
480#endif
481
482#if UNITY_EDITOR
483 // We need this to be queried each domain reload in a static constructor so that it is called on the main thread only!
484 internal static readonly bool IsScriptDebugInfoEnabled = UnityEditor.Compilation.CompilationPipeline.IsScriptDebugInfoEnabled();
485
486 private sealed class DomainReloadStateSingleton : UnityEditor.ScriptableSingleton<DomainReloadStateSingleton>
487 {
488 public bool AlreadyLoaded = false;
489 public bool IsScriptDebugInfoEnabled = false;
490 }
491
492 internal static bool WasScriptDebugInfoEnabledAtDomainReload => DomainReloadStateSingleton.instance.IsScriptDebugInfoEnabled;
493
494 internal static void DomainReload()
495 {
496 const string parameterSeparator = "***";
497 const string assemblySeparator = "```";
498
499 var isScriptDebugInfoEnabled = IsScriptDebugInfoEnabled;
500
501 var cmdBuilder =
502 BeginCompilerCommand(BurstCompilerOptions.CompilerCommandDomainReload)
503 .With(ProgressCallbackFunctionPointer)
504 .With(parameterSeparator)
505 .With(EagerCompileLogCallbackFunctionPointer)
506 .With(parameterSeparator)
507 .With(isScriptDebugInfoEnabled ? "Debug" : "Release")
508 .With(parameterSeparator);
509
510 // We need to send the list of assemblies if
511 // (a) we have never done that before in this Editor instance, or
512 // (b) we have done it before, but now the scripting code optimization mode has changed
513 // from Debug to Release or vice-versa.
514 // This is because these are the two cases in which CompilerClient will be
515 // destroyed and recreated.
516 if (!DomainReloadStateSingleton.instance.AlreadyLoaded ||
517 DomainReloadStateSingleton.instance.IsScriptDebugInfoEnabled != isScriptDebugInfoEnabled)
518 {
519 // Gather list of assemblies to compile (only actually used at Editor startup)
520 var assemblyNames = UnityEditor.Compilation.CompilationPipeline
521 .GetAssemblies(UnityEditor.Compilation.AssembliesType.Editor)
522 .Where(x => File.Exists(x.outputPath)) // If C# compilation fails, it won't exist on disk
523 .Select(x => $"{x.name}|{string.Join(";", x.defines)}");
524
525 foreach (var assemblyName in assemblyNames)
526 {
527 cmdBuilder.With(assemblyName)
528 .With(assemblySeparator);
529 }
530
531 DomainReloadStateSingleton.instance.AlreadyLoaded = true;
532 DomainReloadStateSingleton.instance.IsScriptDebugInfoEnabled = IsScriptDebugInfoEnabled;
533 }
534
535 cmdBuilder.SendToCompiler();
536 }
537
538 internal static string VersionNotify(string version)
539 {
540 return SendCommandToCompiler(BurstCompilerOptions.CompilerCommandVersionNotification, version);
541 }
542
543 internal static string GetTargetCpuFromHost()
544 {
545 return SendCommandToCompiler(BurstCompilerOptions.CompilerCommandGetTargetCpuFromHost);
546 }
547#endif
548
549 /// <summary>
550 /// Cancel any compilation being processed by the JIT Compiler in the background.
551 /// </summary>
552 internal static void Cancel()
553 {
554#if UNITY_EDITOR
555 SendCommandToCompiler(BurstCompilerOptions.CompilerCommandCancel);
556#endif
557 }
558
559 /// <summary>
560 /// Check if there is any job pending related to the last compilation ID.
561 /// </summary>
562 internal static bool IsCurrentCompilationDone()
563 {
564#if UNITY_EDITOR
565 return SendCommandToCompiler(BurstCompilerOptions.CompilerCommandIsCurrentCompilationDone) == "True";
566#else
567 return true;
568#endif
569 }
570
571 internal static void Enable()
572 {
573#if UNITY_EDITOR
574 SendCommandToCompiler(BurstCompilerOptions.CompilerCommandEnableCompiler);
575#endif
576 }
577
578 internal static void Disable()
579 {
580#if UNITY_EDITOR
581 SendCommandToCompiler(BurstCompilerOptions.CompilerCommandDisableCompiler);
582#endif
583 }
584
585 internal static bool IsHostEditorArm()
586 {
587#if UNITY_EDITOR
588 return SendCommandToCompiler(BurstCompilerOptions.CompilerCommandIsArmTestEnv)=="true";
589#else
590 return false;
591#endif
592 }
593
594 internal static void TriggerUnsafeStaticMethodRecompilation()
595 {
596 foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
597 {
598 var reinitAttributes = asm.GetCustomAttributes().Where(
599 x => x.GetType().FullName == "Unity.Burst.BurstCompiler+StaticTypeReinitAttribute"
600 );
601 foreach (var attribute in reinitAttributes)
602 {
603 var ourAttribute = attribute as StaticTypeReinitAttribute;
604 var type = ourAttribute.reinitType;
605 var method = type.GetMethod("Constructor",BindingFlags.Static|BindingFlags.Public);
606 method.Invoke(null, new object[] { });
607 }
608 }
609 }
610
611 internal static void TriggerRecompilation()
612 {
613#if UNITY_EDITOR
614 SetDefaultOptions();
615
616 // This is done separately from CompilerCommandTriggerRecompilation below,
617 // because CompilerCommandTriggerRecompilation will cause all jobs to re-request
618 // their function pointers from Burst, and we need to have actually triggered
619 // compilation by that point.
620 SendCommandToCompiler(BurstCompilerOptions.CompilerCommandTriggerSetupRecompilation);
621
622 SendCommandToCompiler(BurstCompilerOptions.CompilerCommandTriggerRecompilation, Options.RequiresSynchronousCompilation.ToString());
623#endif
624 }
625
626 internal static void UnloadAdditionalLibraries()
627 {
628 SendCommandToCompiler(BurstCompilerOptions.CompilerCommandUnloadBurstNatives);
629 }
630
631 internal static void InitialiseDebuggerHooks()
632 {
633 if (IsApiAvailable("BurstManagedDebuggerPluginV1") && String.IsNullOrEmpty(Environment.GetEnvironmentVariable("BURST_DISABLE_DEBUGGER_HOOKS")))
634 {
635 SendCommandToCompiler(SendCommandToCompiler(BurstCompilerOptions.CompilerCommandRequestInitialiseDebuggerCommmand));
636 }
637 }
638
639 internal static bool IsApiAvailable(string apiName)
640 {
641 return SendCommandToCompiler(BurstCompilerOptions.CompilerCommandIsNativeApiAvailable, apiName) == "True";
642 }
643
644 internal static int RequestSetProtocolVersion(int version)
645 {
646 // Ask editor for the maximum version of the protocol we support, then inform the rest of the systems the negotiated version
647 var editorVersion = SendCommandToCompiler(BurstCompilerOptions.CompilerCommandRequestSetProtocolVersionEditor, $"{version}");
648 if (string.IsNullOrEmpty(editorVersion) || !int.TryParse(editorVersion, out var result))
649 {
650 result=0;
651 }
652 SendCommandToCompiler(BurstCompilerOptions.CompilerCommandSetProtocolVersionBurst, $"{result}");
653 return result;
654 }
655
656
657
658#if UNITY_EDITOR
659 private unsafe delegate void LogCallbackDelegate(void* userData, int logType, byte* message, byte* fileName, int lineNumber);
660
661 private static unsafe void EagerCompileLogCallback(void* userData, int logType, byte* message, byte* fileName, int lineNumber)
662 {
663 if (EagerCompilationLoggingEnabled)
664 {
665 BurstRuntime.Log(message, logType, fileName, lineNumber);
666 }
667 }
668
669
670 [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
671 private delegate IntPtr ManagedFnPtrResolverDelegate(IntPtr handleVal);
672
673 private static IntPtr ManagedResolverFunction(IntPtr handleVal)
674 {
675 var delegateObj = GCHandle.FromIntPtr(handleVal).Target;
676 var fnptr = Marshal.GetFunctionPointerForDelegate(delegateObj);
677 return fnptr;
678 }
679
680 internal static bool EagerCompilationLoggingEnabled = false;
681
682 private static readonly string EagerCompileLogCallbackFunctionPointer;
683 private static readonly string ManagedResolverFunctionPointer;
684#endif
685
686 internal static void Initialize(string[] assemblyFolders, string[] ignoreAssemblies)
687 {
688#if UNITY_EDITOR
689 var glued = new string[2];
690 glued[0] = SafeStringArrayHelper.SerialiseStringArraySafe(assemblyFolders);
691 glued[1] = SafeStringArrayHelper.SerialiseStringArraySafe(ignoreAssemblies);
692 var optionsSet = SafeStringArrayHelper.SerialiseStringArraySafe(glued);
693 SendCommandToCompiler(BurstCompilerOptions.CompilerCommandInitialize, optionsSet);
694#endif
695 }
696
697 internal static void NotifyCompilationStarted(string[] assemblyFolders, string[] ignoreAssemblies)
698 {
699#if UNITY_EDITOR
700 var glued = new string[2];
701 glued[0] = SafeStringArrayHelper.SerialiseStringArraySafe(assemblyFolders);
702 glued[1] = SafeStringArrayHelper.SerialiseStringArraySafe(ignoreAssemblies);
703 var optionsSet = SafeStringArrayHelper.SerialiseStringArraySafe(glued);
704 SendCommandToCompiler(BurstCompilerOptions.CompilerCommandNotifyCompilationStarted, optionsSet);
705#endif
706 }
707
708 internal static void NotifyAssemblyCompilationNotRequired(string assemblyName)
709 {
710#if UNITY_EDITOR
711 SendCommandToCompiler(BurstCompilerOptions.CompilerCommandNotifyAssemblyCompilationNotRequired, assemblyName);
712#endif
713 }
714
715 internal static void NotifyAssemblyCompilationFinished(string assemblyName, string[] defines)
716 {
717#if UNITY_EDITOR
718 BeginCompilerCommand(BurstCompilerOptions.CompilerCommandNotifyAssemblyCompilationFinished)
719 .With(assemblyName).And()
720 .With(string.Join(";", defines))
721 .SendToCompiler();
722#endif
723 }
724
725 internal static void NotifyCompilationFinished()
726 {
727#if UNITY_EDITOR
728 SendCommandToCompiler(BurstCompilerOptions.CompilerCommandNotifyCompilationFinished);
729#endif
730 }
731
732 internal static string AotCompilation(string[] assemblyFolders, string[] assemblyRoots, string options)
733 {
734 var result = "failed";
735#if UNITY_EDITOR
736 result = SendCommandToCompiler(
737 BurstCompilerOptions.CompilerCommandAotCompilation,
738 BurstCompilerOptions.SerialiseCompilationOptionsSafe(assemblyRoots, assemblyFolders, options));
739#endif
740 return result;
741 }
742
743#if UNITY_EDITOR
744 private static readonly string ProgressCallbackFunctionPointer;
745
746 private delegate void ProgressCallbackDelegate(int current, int total);
747
748 private static void ProgressCallback(int current, int total)
749 {
750 OnProgress?.Invoke(current, total);
751 }
752
753 internal static event Action<int, int> OnProgress;
754#endif
755
756 internal static void SetProfilerCallbacks()
757 {
758#if UNITY_EDITOR
759 BeginCompilerCommand(BurstCompilerOptions.CompilerCommandSetProfileCallbacks)
760 .With(ProfileBeginCallbackFunctionPointer).And(';')
761 .With(ProfileEndCallbackFunctionPointer)
762 .SendToCompiler();
763#endif
764 }
765
766#if UNITY_EDITOR
767 internal delegate void ProfileBeginCallbackDelegate(string markerName, string metadataName, string metadataValue);
768 internal delegate void ProfileEndCallbackDelegate(string markerName);
769
770 private static readonly string ProfileBeginCallbackFunctionPointer;
771 private static readonly string ProfileEndCallbackFunctionPointer;
772
773 private static void ProfileBeginCallback(string markerName, string metadataName, string metadataValue) => OnProfileBegin?.Invoke(markerName, metadataName, metadataValue);
774 private static void ProfileEndCallback(string markerName) => OnProfileEnd?.Invoke(markerName);
775
776 internal static event ProfileBeginCallbackDelegate OnProfileBegin;
777 internal static event ProfileEndCallbackDelegate OnProfileEnd;
778#endif
779
780
781 private static string SendRawCommandToCompiler(string command)
782 {
783 var results = Unity.Burst.LowLevel.BurstCompilerService.GetDisassembly(DummyMethodInfo, command);
784 if (!string.IsNullOrEmpty(results))
785 return results.TrimStart('\n');
786 return "";
787 }
788
789 private static string SendCommandToCompiler(string commandName, string commandArgs = null)
790 {
791 if (commandName == null) throw new ArgumentNullException(nameof(commandName));
792
793 if (commandArgs == null)
794 {
795 // If there are no arguments then there's no reason to go through the builder
796 return SendRawCommandToCompiler(commandName);
797 }
798
799 // Otherwise use the builder for building the final command
800 return BeginCompilerCommand(commandName)
801 .With(commandArgs)
802 .SendToCompiler();
803 }
804
805 private static readonly MethodInfo DummyMethodInfo = typeof(BurstCompiler).GetMethod(nameof(DummyMethod), BindingFlags.Static | BindingFlags.NonPublic);
806
807 /// <summary>
808 /// Dummy empty method for being able to send a command to the compiler
809 /// </summary>
810 private static void DummyMethod() { }
811
812#if !UNITY_EDITOR && !BURST_INTERNAL
813 /// <summary>
814 /// Internal class to detect at standalone player time if AOT settings were enabling burst.
815 /// </summary>
816 [BurstCompile]
817 internal static class BurstCompilerHelper
818 {
819 [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
820 private delegate bool IsBurstEnabledDelegate();
821 private static readonly IsBurstEnabledDelegate IsBurstEnabledImpl = new IsBurstEnabledDelegate(IsBurstEnabled);
822
823 [BurstCompile]
824 [AOT.MonoPInvokeCallback(typeof(IsBurstEnabledDelegate))]
825 private static bool IsBurstEnabled()
826 {
827 bool result = true;
828 DiscardedMethod(ref result);
829 return result;
830 }
831
832 [BurstDiscard]
833 private static void DiscardedMethod(ref bool value)
834 {
835 value = false;
836 }
837
838 private static unsafe bool IsCompiledByBurst(Delegate del)
839 {
840 var delegateMethodId = Unity.Burst.LowLevel.BurstCompilerService.CompileAsyncDelegateMethod(del, string.Empty);
841 // We don't try to run the method, having a pointer is already enough to tell us that burst was active for AOT settings
842 return Unity.Burst.LowLevel.BurstCompilerService.GetAsyncCompiledAsyncDelegateMethod(delegateMethodId) != (void*)0;
843 }
844
845 /// <summary>
846 /// Gets a boolean indicating whether burst was enabled for standalone player, used only at runtime.
847 /// </summary>
848 public static readonly bool IsBurstGenerated = IsCompiledByBurst(IsBurstEnabledImpl);
849 }
850#endif // !UNITY_EDITOR && !BURST_INTERNAL
851
852 /// <summary>
853 /// Fake delegate class to make BurstCompilerService.CompileAsyncDelegateMethod happy
854 /// so that it can access the underlying static method via the property get_Method.
855 /// So this class is not a delegate.
856 /// </summary>
857 private class FakeDelegate
858 {
859 public FakeDelegate(MethodInfo method)
860 {
861 Method = method;
862 }
863
864 [Preserve]
865 public MethodInfo Method { get; }
866 }
867 }
868}