Singularity/Library/PackageCache/com.unity.burst@1.8.4/Runtime/BurstCompiler.cs

879 lines
36 KiB
C#
Raw Normal View History

2024-05-06 14:45:45 -04:00
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
#if !UNITY_DOTSPLAYER && !NET_DOTS
using System.Collections.Generic;
using UnityEngine.Scripting;
using System.Linq;
#endif
using System.Text;
namespace Unity.Burst
{
/// <summary>
/// The burst compiler runtime frontend.
/// </summary>
///
public static class BurstCompiler
{
/// <summary>
/// Check if the LoadAdditionalLibrary API is supported by the current version of Unity
/// </summary>
/// <returns>True if the LoadAdditionalLibrary API can be used by the current version of Unity</returns>
public static bool IsLoadAdditionalLibrarySupported()
{
return IsApiAvailable("LoadBurstLibrary");
}
#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>(TDelegate callback)
{
GCHandle.Alloc(callback); // Ensure delegate is never garbage-collected.
var callbackFunctionPointer = Marshal.GetFunctionPointerForDelegate(callback);
return "0x" + callbackFunctionPointer.ToInt64().ToString("X16");
}
EagerCompileLogCallbackFunctionPointer = GetFunctionPointer<LogCallbackDelegate>(EagerCompileLogCallback);
ManagedResolverFunctionPointer = GetFunctionPointer<ManagedFnPtrResolverDelegate>(ManagedResolverFunction);
ProgressCallbackFunctionPointer = GetFunctionPointer<ProgressCallbackDelegate>(ProgressCallback);
ProfileBeginCallbackFunctionPointer = GetFunctionPointer<ProfileBeginCallbackDelegate>(ProfileBeginCallback);
ProfileEndCallbackFunctionPointer = GetFunctionPointer<ProfileEndCallbackDelegate>(ProfileEndCallback);
}
#endif
private class CommandBuilder
{
private StringBuilder _builder;
private bool _hasArgs;
public CommandBuilder()
{
_builder = new StringBuilder();
_hasArgs = false;
}
public CommandBuilder Begin(string cmd)
{
_builder.Clear();
_hasArgs = false;
_builder.Append(cmd);
return this;
}
public CommandBuilder With(string arg)
{
if (!_hasArgs) _builder.Append(' ');
_hasArgs = true;
_builder.Append(arg);
return this;
}
public CommandBuilder With(IntPtr arg)
{
if (!_hasArgs) _builder.Append(' ');
_hasArgs = true;
_builder.AppendFormat("0x{0:X16}", arg.ToInt64());
return this;
}
public CommandBuilder And(char sep = '|')
{
_builder.Append(sep);
return this;
}
public string SendToCompiler()
{
return SendRawCommandToCompiler(_builder.ToString());
}
}
[ThreadStatic]
private static CommandBuilder _cmdBuilder;
private static CommandBuilder BeginCompilerCommand(string cmd)
{
if (_cmdBuilder == null)
{
_cmdBuilder = new CommandBuilder();
}
return _cmdBuilder.Begin(cmd);
}
#if BURST_INTERNAL
[ThreadStatic]
public static Func<object, IntPtr> InternalCompiler;
#endif
/// <summary>
/// Internal variable setup by BurstCompilerOptions.
/// </summary>
#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
#else
internal
#endif
static bool _IsEnabled;
/// <summary>
/// Gets a value indicating whether Burst is enabled.
/// </summary>
#if UNITY_EDITOR || BURST_INTERNAL
public static bool IsEnabled => _IsEnabled;
#else
public static bool IsEnabled => _IsEnabled && BurstCompilerHelper.IsBurstGenerated;
#endif
/// <summary>
/// Gets the global options for the burst compiler.
/// </summary>
public static readonly BurstCompilerOptions Options = new BurstCompilerOptions(true);
/// <summary>
/// Sets the execution mode for all jobs spawned from now on.
/// </summary>
/// <param name="mode">Specifiy the required execution mode</param>
public static void SetExecutionMode(BurstExecutionEnvironment mode)
{
Burst.LowLevel.BurstCompilerService.SetCurrentExecutionMode((uint)mode);
}
/// <summary>
/// Retrieve the current execution mode that is configured.
/// </summary>
/// <returns>Currently configured execution mode</returns>
public static BurstExecutionEnvironment GetExecutionMode()
{
return (BurstExecutionEnvironment)Burst.LowLevel.BurstCompilerService.GetCurrentExecutionMode();
}
/// <summary>
/// Compile the following delegate with burst and return a new delegate.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="delegateMethod"></param>
/// <returns></returns>
/// <remarks>NOT AVAILABLE, unsafe to use</remarks>
internal static unsafe T CompileDelegate<T>(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>(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}'");
}
}
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
private static void VerifyDelegateHasCorrectUnmanagedFunctionPointerAttribute<T>(T delegateMethod) where T : class
{
var attrib = delegateMethod.GetType().GetCustomAttribute<System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute>();
if (attrib == null || attrib.CallingConvention != CallingConvention.Cdecl)
{
#if !BURST_INTERNAL
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.");
#endif
}
}
/// <summary>
/// DO NOT USE - deprecated.
/// </summary>
/// <param name="burstMethodHandle">The Burst method to compile.</param>
/// <param name="managedMethodHandle">The fallback managed method to use.</param>
/// <param name="delegateTypeHandle">The type of the delegate used to execute these methods.</param>
/// <returns>Nothing</returns>
[Obsolete("This method will be removed in a future version of Burst")]
public static unsafe IntPtr CompileILPPMethod(RuntimeMethodHandle burstMethodHandle, RuntimeMethodHandle managedMethodHandle, RuntimeTypeHandle delegateTypeHandle)
{
throw new NotImplementedException();
}
/// <summary>
/// Compile an IL Post-Processed method.
/// </summary>
/// <param name="burstMethodHandle">The Burst method to compile.</param>
/// <returns>A token that must be passed to <see cref="GetILPPMethodFunctionPointer2"/> to get an actual executable function pointer.</returns>
public static unsafe IntPtr CompileILPPMethod2(RuntimeMethodHandle burstMethodHandle)
{
if (burstMethodHandle.Value == IntPtr.Zero)
{
throw new ArgumentNullException(nameof(burstMethodHandle));
}
OnCompileILPPMethod2?.Invoke();
var burstMethod = (MethodInfo)MethodBase.GetMethodFromHandle(burstMethodHandle);
return (IntPtr)Compile(new FakeDelegate(burstMethod), burstMethod, isFunctionPointer: true, isILPostProcessing: true);
}
internal static Action OnCompileILPPMethod2;
/// <summary>
/// DO NOT USE - deprecated.
/// </summary>
/// <param name="ilppMethod">The result of a previous call to <see cref="CompileILPPMethod"/>.</param>
/// <returns>Nothing.</returns>
[Obsolete("This method will be removed in a future version of Burst")]
public static unsafe void* GetILPPMethodFunctionPointer(IntPtr ilppMethod)
{
throw new NotImplementedException();
}
/// <summary>
/// For a previous call to <see cref="CompileILPPMethod2"/>, get the actual executable function pointer.
/// </summary>
/// <param name="ilppMethod">The result of a previous call to <see cref="CompileILPPMethod"/>.</param>
/// <param name="managedMethodHandle">The fallback managed method to use.</param>
/// <param name="delegateTypeHandle">The type of the delegate used to execute these methods.</param>
/// <returns>A pointer into an executable region, for running the function pointer.</returns>
public static unsafe void* GetILPPMethodFunctionPointer2(IntPtr ilppMethod, RuntimeMethodHandle managedMethodHandle, RuntimeTypeHandle delegateTypeHandle)
{
if (ilppMethod == IntPtr.Zero)
{
throw new ArgumentNullException(nameof(ilppMethod));
}
if (managedMethodHandle.Value == IntPtr.Zero)
{
throw new ArgumentNullException(nameof(managedMethodHandle));
}
if (delegateTypeHandle.Value == IntPtr.Zero)
{
throw new ArgumentNullException(nameof(delegateTypeHandle));
}
// If we are in the editor, we need to route a command to the compiler to start compiling the deferred ILPP compilation.
// Otherwise if we're in Burst's internal testing, or in a player build, we already actually have the actual executable
// pointer address, and we just return that.
#if UNITY_EDITOR
var managedMethod = (MethodInfo)MethodBase.GetMethodFromHandle(managedMethodHandle);
var delegateType = Type.GetTypeFromHandle(delegateTypeHandle);
var managedFallbackDelegate = Delegate.CreateDelegate(delegateType, managedMethod);
var handle = GCHandle.Alloc(managedFallbackDelegate);
var result =
BeginCompilerCommand(BurstCompilerOptions.CompilerCommandILPPCompilation)
.With(ilppMethod).And()
.With(ManagedResolverFunctionPointer).And()
.With(GCHandle.ToIntPtr(handle))
.SendToCompiler();
return new IntPtr(Convert.ToInt64(result, 16)).ToPointer();
#else
return ilppMethod.ToPointer();
#endif
}
/// <summary>
/// DO NOT USE - deprecated.
/// </summary>
/// <param name="handle">A runtime method handle.</param>
/// <returns>Nothing.</returns>
[Obsolete("This method will be removed in a future version of Burst")]
public static unsafe void* CompileUnsafeStaticMethod(RuntimeMethodHandle handle)
{
throw new NotImplementedException();
}
/// <summary>
/// Compile the following delegate into a function pointer with burst, invokable from a Burst Job or from regular C#.
/// </summary>
/// <typeparam name="T">Type of the delegate of the function pointer</typeparam>
/// <param name="delegateMethod">The delegate to compile</param>
/// <returns>A function pointer invokable from a Burst Job or from regular C#</returns>
public static unsafe FunctionPointer<T> CompileFunctionPointer<T>(T delegateMethod) where T : class
{
VerifyDelegateIsNotMulticast<T>(delegateMethod);
VerifyDelegateHasCorrectUnmanagedFunctionPointerAttribute<T>(delegateMethod);
// We have added support for runtime CompileDelegate in 2018.2+
void* function = Compile(delegateMethod, true);
return new FunctionPointer<T>(new IntPtr(function));
}
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
internal class StaticTypeReinitAttribute : Attribute
{
public readonly Type reinitType;
public StaticTypeReinitAttribute(Type toReinit)
{
reinitType = toReinit;
}
}
private static unsafe void* Compile(object delegateObj, bool isFunctionPointer)
{
if (!(delegateObj is Delegate)) throw new ArgumentException("object instance must be a System.Delegate", nameof(delegateObj));
var delegateMethod = (Delegate)delegateObj;
return Compile(delegateMethod, delegateMethod.Method, isFunctionPointer, false);
}
private static unsafe void* Compile(object delegateObj, MethodInfo methodInfo, bool isFunctionPointer, bool isILPostProcessing)
{
if (delegateObj == null) throw new ArgumentNullException(nameof(delegateObj));
if (delegateObj.GetType().IsGenericType)
{
throw new InvalidOperationException($"The delegate type `{delegateObj.GetType()}` must be a non-generic type");
}
if (!methodInfo.IsStatic)
{
throw new InvalidOperationException($"The method `{methodInfo}` must be static. Instance methods are not supported");
}
if (methodInfo.IsGenericMethod)
{
throw new InvalidOperationException($"The method `{methodInfo}` must be a non-generic method");
}
#if ENABLE_IL2CPP
if (isFunctionPointer && !isILPostProcessing &&
methodInfo.GetCustomAttributes().All(s => s.GetType().Name != "MonoPInvokeCallbackAttribute"))
{
UnityEngine.Debug.Log($"The method `{methodInfo}` 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*)InternalCompiler(delegateObj);
#else
Delegate managedFallbackDelegateMethod = null;
if (!isILPostProcessing)
{
managedFallbackDelegateMethod = delegateObj as Delegate;
}
var delegateMethod = delegateObj as Delegate;
#if UNITY_EDITOR
string defaultOptions;
// In case Burst is disabled entirely from the command line
if (BurstCompilerOptions.ForceDisableBurstCompilation)
{
if (isILPostProcessing)
{
return null;
}
else
{
GCHandle.Alloc(managedFallbackDelegateMethod);
function = (void*)Marshal.GetFunctionPointerForDelegate(managedFallbackDelegateMethod);
return function;
}
}
if (isILPostProcessing)
{
defaultOptions = "--" + BurstCompilerOptions.OptionJitIsForFunctionPointer + "\n";
}
else if (isFunctionPointer)
{
defaultOptions = "--" + BurstCompilerOptions.OptionJitIsForFunctionPointer + "\n";
// Make sure that the delegate will never be collected
var delHandle = GCHandle.Alloc(managedFallbackDelegateMethod);
defaultOptions += "--" + BurstCompilerOptions.OptionJitManagedDelegateHandle + "0x" + ManagedResolverFunctionPointer + "|" + "0x" + GCHandle.ToIntPtr(delHandle).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(methodInfo, true, out extraOptions, isForILPostProcessing: isILPostProcessing))
{
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(methodInfo))
{
if (Options.EnableBurstCompilation && BurstCompilerHelper.IsBurstGenerated)
{
var delegateMethodId = Unity.Burst.LowLevel.BurstCompilerService.CompileAsyncDelegateMethod(delegateObj, string.Empty);
function = Unity.Burst.LowLevel.BurstCompilerService.GetAsyncCompiledAsyncDelegateMethod(delegateMethodId);
}
else
{
// If this is for direct-call, and we're in a player, with Burst disabled, then we should return null,
// since we don't actually have a managedFallbackDelegateMethod at this point.
if (isILPostProcessing)
{
return null;
}
// Make sure that the delegate will never be collected
GCHandle.Alloc(managedFallbackDelegateMethod);
// 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(managedFallbackDelegateMethod);
}
}
#endif
else
{
throw new InvalidOperationException($"Burst cannot compile the function pointer `{methodInfo}` 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 `{methodInfo}`");
}
// 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;
}
/// <summary>
/// Lets the compiler service know we are shutting down, called by the event on OnDomainUnload, if EditorApplication.quitting was called
/// </summary>
internal static void Shutdown()
{
#if UNITY_EDITOR
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandShutdown);
#endif
}
#if UNITY_EDITOR
internal static void SetDefaultOptions()
{
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandSetDefaultOptions, Options.GetOptions(true, isForCompilerClient: true));
}
#endif
#if UNITY_EDITOR
// We need this to be queried each domain reload in a static constructor so that it is called on the main thread only!
internal static readonly bool IsScriptDebugInfoEnabled = UnityEditor.Compilation.CompilationPipeline.IsScriptDebugInfoEnabled();
private sealed class DomainReloadStateSingleton : UnityEditor.ScriptableSingleton<DomainReloadStateSingleton>
{
public bool AlreadyLoaded = false;
public bool IsScriptDebugInfoEnabled = false;
}
internal static bool WasScriptDebugInfoEnabledAtDomainReload => DomainReloadStateSingleton.instance.IsScriptDebugInfoEnabled;
internal static void DomainReload()
{
const string parameterSeparator = "***";
const string assemblySeparator = "```";
var isScriptDebugInfoEnabled = IsScriptDebugInfoEnabled;
var cmdBuilder =
BeginCompilerCommand(BurstCompilerOptions.CompilerCommandDomainReload)
.With(ProgressCallbackFunctionPointer)
.With(parameterSeparator)
.With(EagerCompileLogCallbackFunctionPointer)
.With(parameterSeparator)
.With(isScriptDebugInfoEnabled ? "Debug" : "Release")
.With(parameterSeparator);
// We need to send the list of assemblies if
// (a) we have never done that before in this Editor instance, or
// (b) we have done it before, but now the scripting code optimization mode has changed
// from Debug to Release or vice-versa.
// This is because these are the two cases in which CompilerClient will be
// destroyed and recreated.
if (!DomainReloadStateSingleton.instance.AlreadyLoaded ||
DomainReloadStateSingleton.instance.IsScriptDebugInfoEnabled != isScriptDebugInfoEnabled)
{
// Gather list of assemblies to compile (only actually used at Editor startup)
var assemblyNames = UnityEditor.Compilation.CompilationPipeline
.GetAssemblies(UnityEditor.Compilation.AssembliesType.Editor)
.Where(x => File.Exists(x.outputPath)) // If C# compilation fails, it won't exist on disk
.Select(x => x.name);
foreach (var assemblyName in assemblyNames)
{
cmdBuilder.With(assemblyName)
.With(assemblySeparator);
}
DomainReloadStateSingleton.instance.AlreadyLoaded = true;
DomainReloadStateSingleton.instance.IsScriptDebugInfoEnabled = IsScriptDebugInfoEnabled;
}
cmdBuilder.SendToCompiler();
}
internal static string VersionNotify(string version)
{
return SendCommandToCompiler(BurstCompilerOptions.CompilerCommandVersionNotification, version);
}
#endif
/// <summary>
/// Cancel any compilation being processed by the JIT Compiler in the background.
/// </summary>
internal static void Cancel()
{
#if UNITY_EDITOR
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandCancel);
#endif
}
/// <summary>
/// Check if there is any job pending related to the last compilation ID.
/// </summary>
internal static bool IsCurrentCompilationDone()
{
#if UNITY_EDITOR
return SendCommandToCompiler(BurstCompilerOptions.CompilerCommandIsCurrentCompilationDone) == "True";
#else
return true;
#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 bool IsHostEditorArm()
{
#if UNITY_EDITOR
return SendCommandToCompiler(BurstCompilerOptions.CompilerCommandIsArmTestEnv)=="true";
#else
return false;
#endif
}
internal static void TriggerUnsafeStaticMethodRecompilation()
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
var reinitAttributes = asm.GetCustomAttributes().Where(
x => x.GetType().FullName == "Unity.Burst.BurstCompiler+StaticTypeReinitAttribute"
);
foreach (var attribute in reinitAttributes)
{
var ourAttribute = attribute as StaticTypeReinitAttribute;
var type = ourAttribute.reinitType;
var method = type.GetMethod("Constructor",BindingFlags.Static|BindingFlags.Public);
method.Invoke(null, new object[] { });
}
}
}
internal static void TriggerRecompilation()
{
#if UNITY_EDITOR
SetDefaultOptions();
// This is done separately from CompilerCommandTriggerRecompilation below,
// because CompilerCommandTriggerRecompilation will cause all jobs to re-request
// their function pointers from Burst, and we need to have actually triggered
// compilation by that point.
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandTriggerSetupRecompilation);
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandTriggerRecompilation, Options.GetOptions(true));
#endif
}
internal static void UnloadAdditionalLibraries()
{
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandUnloadBurstNatives);
}
internal static void InitialiseDebuggerHooks()
{
if (IsApiAvailable("BurstManagedDebuggerPluginV1"))
{
SendCommandToCompiler(SendCommandToCompiler(BurstCompilerOptions.CompilerCommandRequestInitialiseDebuggerCommmand));
}
}
internal static bool IsApiAvailable(string apiName)
{
return SendCommandToCompiler(BurstCompilerOptions.CompilerCommandIsNativeApiAvailable, apiName) == "True";
}
internal static int RequestSetProtocolVersion(int version)
{
// Ask editor for the maximum version of the protocol we support, then inform the rest of the systems the negotiated version
var editorVersion = SendCommandToCompiler(BurstCompilerOptions.CompilerCommandRequestSetProtocolVersionEditor, $"{version}");
if (string.IsNullOrEmpty(editorVersion) || !int.TryParse(editorVersion, out var result))
{
result=0;
}
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandSetProtocolVersionBurst, $"{result}");
return result;
}
#if UNITY_EDITOR
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);
}
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate IntPtr ManagedFnPtrResolverDelegate(IntPtr handleVal);
private static IntPtr ManagedResolverFunction(IntPtr handleVal)
{
var delegateObj = GCHandle.FromIntPtr(handleVal).Target;
var fnptr = Marshal.GetFunctionPointerForDelegate(delegateObj);
return fnptr;
}
internal static bool EagerCompilationLoggingEnabled = false;
private static readonly string EagerCompileLogCallbackFunctionPointer;
private static readonly string ManagedResolverFunctionPointer;
#endif
internal static void Initialize(string[] assemblyFolders)
{
#if UNITY_EDITOR
var assemblyFoldersSerialized = SafeStringArrayHelper.SerialiseStringArraySafe(assemblyFolders);
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandInitialize, assemblyFoldersSerialized);
#endif
}
internal static void NotifyCompilationStarted(string[] assemblyFolders)
{
#if UNITY_EDITOR
var assemblyFoldersSerialized = SafeStringArrayHelper.SerialiseStringArraySafe(assemblyFolders);
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandNotifyCompilationStarted, assemblyFoldersSerialized);
#endif
}
internal static void NotifyAssemblyCompilationNotRequired(string assemblyName)
{
#if UNITY_EDITOR
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandNotifyAssemblyCompilationNotRequired, assemblyName);
#endif
}
internal static void NotifyAssemblyCompilationFinished(string assemblyName)
{
#if UNITY_EDITOR
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandNotifyAssemblyCompilationFinished, assemblyName);
#endif
}
internal static void NotifyCompilationFinished()
{
#if UNITY_EDITOR
SendCommandToCompiler(BurstCompilerOptions.CompilerCommandNotifyCompilationFinished);
#endif
}
internal static string AotCompilation(string[] assemblyFolders, string[] assemblyRoots, string options)
{
var result = "failed";
#if UNITY_EDITOR
result = SendCommandToCompiler(
BurstCompilerOptions.CompilerCommandAotCompilation,
BurstCompilerOptions.SerialiseCompilationOptionsSafe(assemblyRoots, assemblyFolders, options));
#endif
return result;
}
#if UNITY_EDITOR
private static readonly string ProgressCallbackFunctionPointer;
private delegate void ProgressCallbackDelegate(int current, int total);
private static void ProgressCallback(int current, int total)
{
OnProgress?.Invoke(current, total);
}
internal static event Action<int, int> OnProgress;
#endif
internal static void SetProfilerCallbacks()
{
#if UNITY_EDITOR
BeginCompilerCommand(BurstCompilerOptions.CompilerCommandSetProfileCallbacks)
.With(ProfileBeginCallbackFunctionPointer).And(';')
.With(ProfileEndCallbackFunctionPointer)
.SendToCompiler();
#endif
}
#if UNITY_EDITOR
internal delegate void ProfileBeginCallbackDelegate(string markerName, string metadataName, string metadataValue);
internal delegate void ProfileEndCallbackDelegate(string markerName);
private static readonly string ProfileBeginCallbackFunctionPointer;
private static readonly string ProfileEndCallbackFunctionPointer;
private static void ProfileBeginCallback(string markerName, string metadataName, string metadataValue) => OnProfileBegin?.Invoke(markerName, metadataName, metadataValue);
private static void ProfileEndCallback(string markerName) => OnProfileEnd?.Invoke(markerName);
internal static event ProfileBeginCallbackDelegate OnProfileBegin;
internal static event ProfileEndCallbackDelegate OnProfileEnd;
#endif
private static string SendRawCommandToCompiler(string command)
{
var results = Unity.Burst.LowLevel.BurstCompilerService.GetDisassembly(DummyMethodInfo, command);
if (!string.IsNullOrEmpty(results))
return results.TrimStart('\n');
return "";
}
private static string SendCommandToCompiler(string commandName, string commandArgs = null)
{
if (commandName == null) throw new ArgumentNullException(nameof(commandName));
if (commandArgs == null)
{
// If there are no arguments then there's no reason to go through the builder
return SendRawCommandToCompiler(commandName);
}
// Otherwise use the builder for building the final command
return BeginCompilerCommand(commandName)
.With(commandArgs)
.SendToCompiler();
}
private static readonly MethodInfo DummyMethodInfo = typeof(BurstCompiler).GetMethod(nameof(DummyMethod), BindingFlags.Static | BindingFlags.NonPublic);
/// <summary>
/// Dummy empty method for being able to send a command to the compiler
/// </summary>
private static void DummyMethod() { }
#if !UNITY_EDITOR && !BURST_INTERNAL
/// <summary>
/// Internal class to detect at standalone player time if AOT settings were enabling burst.
/// </summary>
[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;
}
/// <summary>
/// Gets a boolean indicating whether burst was enabled for standalone player, used only at runtime.
/// </summary>
public static readonly bool IsBurstGenerated = IsCompiledByBurst(IsBurstEnabledImpl);
}
#endif // !UNITY_EDITOR && !BURST_INTERNAL
/// <summary>
/// Fake delegate class to make BurstCompilerService.CompileAsyncDelegateMethod happy
/// so that it can access the underlying static method via the property get_Method.
/// So this class is not a delegate.
/// </summary>
private class FakeDelegate
{
public FakeDelegate(MethodInfo method)
{
Method = method;
}
[Preserve]
public MethodInfo Method { get; }
}
#else // UNITY_DOTSPLAYER || NET_DOTS
/// <summary>
/// Compile the following delegate into a function pointer with burst, invokable from a Burst Job or from regular C#.
/// </summary>
/// <typeparam name="T">Type of the delegate of the function pointer</typeparam>
/// <param name="delegateMethod">The delegate to compile</param>
/// <returns>A function pointer invokable from a Burst Job or from regular C#</returns>
public static unsafe FunctionPointer<T> CompileFunctionPointer<T>(T delegateMethod) where T : System.Delegate
{
// Make sure that the delegate will never be collected
GCHandle.Alloc(delegateMethod);
return new FunctionPointer<T>(Marshal.GetFunctionPointerForDelegate(delegateMethod));
}
internal static bool IsApiAvailable(string apiName)
{
return false;
}
#endif
}
}