using System;
using System.Collections.Generic;
using System.Diagnostics;
#if ENABLE_IL2CPP
using System.Linq;
#endif
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
namespace Unity.Burst
{
///
/// The burst compiler runtime frontend.
///
public static class BurstCompiler
{
#if !UNITY_DOTSPLAYER && !NET_DOTS
#if UNITY_EDITOR
static unsafe BurstCompiler()
{
// Store pointers to Log and Compile callback methods.
// For more info about why we need to do this, see comments in CallbackStubManager.
string GetFunctionPointer(TDelegate callback)
{
GCHandle.Alloc(callback); // Ensure delegate is never garbage-collected.
var callbackFunctionPointer = Marshal.GetFunctionPointerForDelegate(callback);
return "0x" + callbackFunctionPointer.ToInt64().ToString("X16");
}
EagerCompileCompileCallbackFunctionPointer = GetFunctionPointer(EagerCompileCompileCallback);
EagerCompileLogCallbackFunctionPointer = GetFunctionPointer(EagerCompileLogCallback);
#if UNITY_2020_1_OR_NEWER
ProgressCallbackFunctionPointer = GetFunctionPointer(ProgressCallback);
#endif
}
#endif
///
/// Gets the global options for the burst compiler.
///
public static readonly BurstCompilerOptions Options = new BurstCompilerOptions(true);
///
/// Internal variable setup by BurstCompilerOptions.
///
#if BURST_INTERNAL
[ThreadStatic] // As we are changing this boolean via BurstCompilerOptions in btests and we are running multithread tests
// we would change a global and it would generate random errors, so specifically for btests, we are using a TLS.
public static bool _IsEnabled;
#else
internal static bool _IsEnabled;
#endif
///
/// Gets a value indicating whether Burst is enabled.
///
#if UNITY_EDITOR || BURST_INTERNAL
public static bool IsEnabled => _IsEnabled;
#else
public static bool IsEnabled => _IsEnabled && BurstCompilerHelper.IsBurstGenerated;
#endif
#if UNITY_2019_3_OR_NEWER
///
/// Sets the execution mode for all jobs spawned from now on.
///
/// Specifiy the required execution mode
public static void SetExecutionMode(BurstExecutionEnvironment mode)
{
Burst.LowLevel.BurstCompilerService.SetCurrentExecutionMode((uint)mode);
}
///
/// Retrieve the current execution mode that is configured.
///
/// Currently configured execution mode
public static BurstExecutionEnvironment GetExecutionMode()
{
return (BurstExecutionEnvironment)Burst.LowLevel.BurstCompilerService.GetCurrentExecutionMode();
}
#endif
///
/// Compile the following delegate with burst and return a new delegate.
///
///
///
///
/// NOT AVAILABLE, unsafe to use
internal static unsafe T CompileDelegate(T delegateMethod) where T : class
{
// We have added support for runtime CompileDelegate in 2018.2+
void* function = Compile(delegateMethod, false);
object res = System.Runtime.InteropServices.Marshal.GetDelegateForFunctionPointer((IntPtr)function, delegateMethod.GetType());
return (T)res;
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
private static void VerifyDelegateIsNotMulticast(T delegateMethod) where T : class
{
var delegateKind = delegateMethod as Delegate;
if (delegateKind.GetInvocationList().Length > 1)
{
throw new InvalidOperationException($"Burst does not support multicast delegates, please use a regular delegate for `{delegateMethod}'");
}
}
///
/// Compile the following delegate into a function pointer with burst, invokable from a Burst Job or from regular C#.
///
/// Type of the delegate of the function pointer
/// The delegate to compile
/// A function pointer invokable from a Burst Job or from regular C#
public static unsafe FunctionPointer CompileFunctionPointer(T delegateMethod) where T : class
{
VerifyDelegateIsNotMulticast(delegateMethod);
// We have added support for runtime CompileDelegate in 2018.2+
void* function = Compile(delegateMethod, true);
return new FunctionPointer(new IntPtr(function));
}
private static unsafe void* Compile(T delegateObj, bool isFunctionPointer) where T : class
{
if (delegateObj == null) throw new ArgumentNullException(nameof(delegateObj));
if (!(delegateObj is Delegate)) throw new ArgumentException("object instance must be a System.Delegate", nameof(delegateObj));
var delegateMethod = (Delegate)(object)delegateObj;
if (!delegateMethod.Method.IsStatic)
{
throw new InvalidOperationException($"The method `{delegateMethod.Method}` must be static. Instance methods are not supported");
}
if (delegateMethod.Method.IsGenericMethod)
{
throw new InvalidOperationException($"The method `{delegateMethod.Method}` must be a non-generic method");
}
#if ENABLE_IL2CPP
if (isFunctionPointer &&
delegateMethod.Method.GetCustomAttributes().All(s => s.GetType().Name != "MonoPInvokeCallbackAttribute"))
{
UnityEngine.Debug.Log($"The method `{delegateMethod.Method}` must have `MonoPInvokeCallback` attribute to be compatible with IL2CPP!");
}
#endif
void* function;
#if BURST_INTERNAL
// Internally in Burst tests, we callback the C# method instead
function = (void*)Marshal.GetFunctionPointerForDelegate(delegateMethod);
#else
#if UNITY_EDITOR
string defaultOptions;
// In case Burst is disabled entirely from the command line
if (BurstCompilerOptions.ForceDisableBurstCompilation)
{
GCHandle.Alloc(delegateMethod);
function = (void*)Marshal.GetFunctionPointerForDelegate(delegateMethod);
return function;
}
if (isFunctionPointer)
{
defaultOptions = "--" + BurstCompilerOptions.OptionJitIsForFunctionPointer + "\n";
// Make sure that the delegate will never be collected
GCHandle.Alloc(delegateMethod);
var managedFunctionPointer = Marshal.GetFunctionPointerForDelegate(delegateMethod);
defaultOptions += "--" + BurstCompilerOptions.OptionJitManagedFunctionPointer + "0x" + managedFunctionPointer.ToInt64().ToString("X16");
}
else
{
defaultOptions = "--" + BurstCompilerOptions.OptionJitEnableSynchronousCompilation;
}
string extraOptions;
// The attribute is directly on the method, so we recover the underlying method here
if (Options.TryGetOptions(delegateMethod.Method, true, out extraOptions))
{
if (!string.IsNullOrWhiteSpace(extraOptions))
{
defaultOptions += "\n" + extraOptions;
}
var delegateMethodId = Unity.Burst.LowLevel.BurstCompilerService.CompileAsyncDelegateMethod(delegateObj, defaultOptions);
function = Unity.Burst.LowLevel.BurstCompilerService.GetAsyncCompiledAsyncDelegateMethod(delegateMethodId);
}
#else
// The attribute is directly on the method, so we recover the underlying method here
if (BurstCompilerOptions.HasBurstCompileAttribute(delegateMethod.Method))
{
if (Options.EnableBurstCompilation && BurstCompilerHelper.IsBurstGenerated)
{
var delegateMethodId = Unity.Burst.LowLevel.BurstCompilerService.CompileAsyncDelegateMethod(delegateObj, string.Empty);
function = Unity.Burst.LowLevel.BurstCompilerService.GetAsyncCompiledAsyncDelegateMethod(delegateMethodId);
}
else
{
// Make sure that the delegate will never be collected
GCHandle.Alloc(delegateMethod);
// If we are in a standalone player, and burst is disabled and we are actually
// trying to load a function pointer, in that case we need to support it
// so we are then going to use the managed function directly
// 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.`
// so in that case, the method needs to have `MonoPInvokeCallback`
// but that's a requirement for IL2CPP, not an issue with burst
function = (void*)Marshal.GetFunctionPointerForDelegate(delegateMethod);
}
}
#endif
else
{
throw new InvalidOperationException($"Burst cannot compile the function pointer `{delegateMethod.Method}` because the `[BurstCompile]` attribute is missing");
}
#endif
// Should not happen but in that case, we are still trying to generated an error
// It can be null if we are trying to compile a function in a standalone player
// and the function was not compiled. In that case, we need to output an error
if (function == null)
{
throw new InvalidOperationException($"Burst failed to compile the function pointer `{delegateMethod.Method}`");
}
// When burst compilation is disabled, we are still returning a valid stub function pointer (the a pointer to the managed function)
// so that CompileFunctionPointer actually returns a delegate in all cases
return function;
}
///
/// Lets the compiler service know we are shutting down, called by the event EditorApplication.quitting
///
internal static void Shutdown()
{
#if UNITY_EDITOR
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandShutdown);
#endif
}
#if UNITY_EDITOR
internal static void DomainReload()
{
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandDomainReload);
}
internal static string VersionNotify(string version)
{
return SendCommandToCompiler(BurstCompilerOptions.CompilerCommandVersionNotification, version);
}
internal static void UpdateAssemblerFolders(List folders)
{
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandUpdateAssemblyFolders, $"{string.Join(";", folders)}");
}
#endif
///
/// Cancel any compilation being processed by the JIT Compiler in the background.
///
internal static void Cancel()
{
#if UNITY_EDITOR
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandCancel);
#endif
}
internal static void Enable()
{
#if UNITY_EDITOR
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandEnableCompiler);
#endif
}
internal static void Disable()
{
#if UNITY_EDITOR
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandDisableCompiler);
#endif
}
internal static void TriggerRecompilation()
{
#if UNITY_EDITOR
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandTriggerRecompilation, Options.GetOptions(true));
#endif
}
internal static void EagerCompileMethods(List requests)
{
#if UNITY_EDITOR
// The order of these arguments MUST match the corresponding code in JitCompilerService.EagerCompileMethods.
const string parameterSeparator = "***";
const string requestParametersSeparator = "+++";
const string methodSeparator = "```";
var builder = new StringBuilder();
builder.Append(EagerCompileCompileCallbackFunctionPointer);
builder.Append(parameterSeparator);
builder.Append(EagerCompileLogCallbackFunctionPointer);
builder.Append(parameterSeparator);
foreach (var request in requests)
{
builder.Append(request.EncodedMethod);
builder.Append(requestParametersSeparator);
builder.Append(request.Options);
builder.Append(methodSeparator);
}
builder.Append(parameterSeparator);
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandEagerCompileMethods, builder.ToString());
#endif
}
#if UNITY_EDITOR
private unsafe delegate void CompileCallbackDelegate(void* userdata, NativeDumpFlags dumpFlags, void* dataPtr);
private static unsafe void EagerCompileCompileCallback(void* userData, NativeDumpFlags dumpFlags, void* dataPtr) { }
private static readonly string EagerCompileCompileCallbackFunctionPointer;
private unsafe delegate void LogCallbackDelegate(void* userData, int logType, byte* message, byte* fileName, int lineNumber);
private static unsafe void EagerCompileLogCallback(void* userData, int logType, byte* message, byte* fileName, int lineNumber)
{
if (EagerCompilationLoggingEnabled)
{
BurstRuntime.Log(message, logType, fileName, lineNumber);
}
}
internal static bool EagerCompilationLoggingEnabled = false;
private static readonly string EagerCompileLogCallbackFunctionPointer;
#endif
internal static void WaitUntilCompilationFinished()
{
#if UNITY_EDITOR
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandWaitUntilCompilationFinished);
#endif
}
internal static void ClearEagerCompilationQueues()
{
#if UNITY_EDITOR
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandClearEagerCompilationQueues);
#endif
}
internal static void CancelEagerCompilation()
{
#if UNITY_EDITOR
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandCancelEagerCompilation);
#endif
}
internal static void SetProgressCallback()
{
#if UNITY_EDITOR && UNITY_2020_1_OR_NEWER
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandSetProgressCallback, ProgressCallbackFunctionPointer);
#endif
}
#if UNITY_EDITOR && UNITY_2020_1_OR_NEWER
private delegate void ProgressCallbackDelegate(int current, int total);
private static readonly string ProgressCallbackFunctionPointer;
private static void ProgressCallback(int current, int total)
{
OnProgress?.Invoke(current, total);
}
internal static event Action OnProgress;
#endif
internal static void RequestClearJitCache()
{
#if UNITY_EDITOR && UNITY_2020_1_OR_NEWER
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandRequestClearJitCache);
#endif
}
internal static void Reset()
{
#if UNITY_EDITOR
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandReset);
#endif
}
#if UNITY_EDITOR || BURST_INTERNAL
private static string SendCommandToCompiler(string commandName, string commandArgs = null)
{
if (commandName == null) throw new ArgumentNullException(nameof(commandName));
var compilerOptions = commandName;
if (commandArgs != null)
{
compilerOptions += " " + commandArgs;
}
var results = Unity.Burst.LowLevel.BurstCompilerService.GetDisassembly(DummyMethodInfo, compilerOptions);
if (!string.IsNullOrEmpty(results))
return results.TrimStart('\n');
return "";
}
private static readonly MethodInfo DummyMethodInfo = typeof(BurstCompiler).GetMethod(nameof(DummyMethod), BindingFlags.Static | BindingFlags.NonPublic);
///
/// Dummy empty method for being able to send a command to the compiler
///
private static void DummyMethod() { }
#else
///
/// Internal class to detect at standalone player time if AOT settings were enabling burst.
///
[BurstCompile]
internal static class BurstCompilerHelper
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate bool IsBurstEnabledDelegate();
private static readonly IsBurstEnabledDelegate IsBurstEnabledImpl = new IsBurstEnabledDelegate(IsBurstEnabled);
[BurstCompile]
[AOT.MonoPInvokeCallback(typeof(IsBurstEnabledDelegate))]
private static bool IsBurstEnabled()
{
bool result = true;
DiscardedMethod(ref result);
return result;
}
[BurstDiscard]
private static void DiscardedMethod(ref bool value)
{
value = false;
}
private static unsafe bool IsCompiledByBurst(Delegate del)
{
var delegateMethodId = Unity.Burst.LowLevel.BurstCompilerService.CompileAsyncDelegateMethod(del, string.Empty);
// We don't try to run the method, having a pointer is already enough to tell us that burst was active for AOT settings
return Unity.Burst.LowLevel.BurstCompilerService.GetAsyncCompiledAsyncDelegateMethod(delegateMethodId) != (void*)0;
}
///
/// Gets a boolean indicating whether burst was enabled for standalone player, used only at runtime.
///
public static readonly bool IsBurstGenerated = IsCompiledByBurst(IsBurstEnabledImpl);
}
#endif
#else // UNITY_DOTSPLAYER || NET_DOTS
///
/// Compile the following delegate into a function pointer with burst, invokable from a Burst Job or from regular C#.
///
/// Type of the delegate of the function pointer
/// The delegate to compile
/// A function pointer invokable from a Burst Job or from regular C#
public static unsafe FunctionPointer CompileFunctionPointer(T delegateMethod) where T : System.Delegate
{
// Make sure that the delegate will never be collected
GCHandle.Alloc(delegateMethod);
return new FunctionPointer(Marshal.GetFunctionPointerForDelegate(delegateMethod));
}
#endif
}
}