2023-03-28 13:24:16 -04:00
using System ;
using System.Diagnostics ;
2023-05-07 18:43:11 -04:00
using System.IO ;
2023-03-28 13:24:16 -04:00
using System.Reflection ;
using System.Runtime.InteropServices ;
#if ! UNITY_DOTSPLAYER & & ! NET_DOTS
2023-05-07 18:43:11 -04:00
using System.Collections.Generic ;
2023-03-28 13:24:16 -04:00
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 ( )
{
2023-05-07 18:43:11 -04:00
SendCommandToCompiler ( BurstCompilerOptions . CompilerCommandSetDefaultOptions , Options . GetOptions ( true , isForCompilerClient : true ) ) ;
2023-03-28 13:24:16 -04:00
}
#endif
#if UNITY_EDITOR
2023-05-07 18:43:11 -04:00
// 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 ;
2023-03-28 13:24:16 -04:00
internal static void DomainReload ( )
{
const string parameterSeparator = "***" ;
const string assemblySeparator = "```" ;
2023-05-07 18:43:11 -04:00
var isScriptDebugInfoEnabled = IsScriptDebugInfoEnabled ;
2023-03-28 13:24:16 -04:00
var cmdBuilder =
BeginCompilerCommand ( BurstCompilerOptions . CompilerCommandDomainReload )
. With ( ProgressCallbackFunctionPointer )
. With ( parameterSeparator )
. With ( EagerCompileLogCallbackFunctionPointer )
2023-05-07 18:43:11 -04:00
. With ( parameterSeparator )
. With ( isScriptDebugInfoEnabled ? "Debug" : "Release" )
2023-03-28 13:24:16 -04:00
. With ( parameterSeparator ) ;
2023-05-07 18:43:11 -04:00
// 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 ) ;
}
2023-03-28 13:24:16 -04:00
2023-05-07 18:43:11 -04:00
DomainReloadStateSingleton . instance . AlreadyLoaded = true ;
DomainReloadStateSingleton . instance . IsScriptDebugInfoEnabled = IsScriptDebugInfoEnabled ;
2023-03-28 13:24:16 -04:00
}
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
}
2023-05-07 18:43:11 -04:00
/// <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
}
2023-03-28 13:24:16 -04:00
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 ) ;
}
2023-05-07 18:43:11 -04:00
internal static void InitialiseDebuggerHooks ( )
{
if ( IsApiAvailable ( "BurstManagedDebuggerPluginV1" ) )
{
SendCommandToCompiler ( SendCommandToCompiler ( BurstCompilerOptions . CompilerCommandRequestInitialiseDebuggerCommmand ) ) ;
}
}
2023-03-28 13:24:16 -04:00
internal static bool IsApiAvailable ( string apiName )
{
return SendCommandToCompiler ( BurstCompilerOptions . CompilerCommandIsNativeApiAvailable , apiName ) = = "True" ;
}
2023-05-07 18:43:11 -04:00
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 ;
}
2023-03-28 13:24:16 -04:00
#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
2023-05-07 18:43:11 -04:00
internal static void Initialize ( string [ ] assemblyFolders )
{
#if UNITY_EDITOR
var assemblyFoldersSerialized = SafeStringArrayHelper . SerialiseStringArraySafe ( assemblyFolders ) ;
SendCommandToCompiler ( BurstCompilerOptions . CompilerCommandInitialize , assemblyFoldersSerialized ) ;
2023-03-28 13:24:16 -04:00
#endif
2023-05-07 18:43:11 -04:00
}
2023-03-28 13:24:16 -04:00
2023-05-07 18:43:11 -04:00
internal static void NotifyCompilationStarted ( string [ ] assemblyFolders )
2023-03-28 13:24:16 -04:00
{
#if UNITY_EDITOR
2023-05-07 18:43:11 -04:00
var assemblyFoldersSerialized = SafeStringArrayHelper . SerialiseStringArraySafe ( assemblyFolders ) ;
SendCommandToCompiler ( BurstCompilerOptions . CompilerCommandNotifyCompilationStarted , assemblyFoldersSerialized ) ;
2023-03-28 13:24:16 -04:00
#endif
2023-05-07 18:43:11 -04:00
}
internal static void NotifyAssemblyCompilationNotRequired ( string assemblyName )
{
#if UNITY_EDITOR
SendCommandToCompiler ( BurstCompilerOptions . CompilerCommandNotifyAssemblyCompilationNotRequired , assemblyName ) ;
2023-03-28 13:24:16 -04:00
#endif
}
internal static void NotifyAssemblyCompilationFinished ( string assemblyName )
{
#if UNITY_EDITOR
SendCommandToCompiler ( BurstCompilerOptions . CompilerCommandNotifyAssemblyCompilationFinished , assemblyName ) ;
#endif
}
internal static void NotifyCompilationFinished ( )
{
#if UNITY_EDITOR
2023-05-07 18:43:11 -04:00
SendCommandToCompiler ( BurstCompilerOptions . CompilerCommandNotifyCompilationFinished ) ;
2023-03-28 13:24:16 -04:00
#endif
2023-05-07 18:43:11 -04:00
}
2023-03-28 13:24:16 -04:00
2023-05-07 18:43:11 -04:00
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 ) ) ;
2023-03-28 13:24:16 -04:00
#endif
2023-05-07 18:43:11 -04:00
return result ;
2023-03-28 13:24:16 -04:00
}
#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 ( )
{
2023-05-07 18:43:11 -04:00
#if UNITY_EDITOR
2023-03-28 13:24:16 -04:00
BeginCompilerCommand ( BurstCompilerOptions . CompilerCommandSetProfileCallbacks )
. With ( ProfileBeginCallbackFunctionPointer ) . And ( ';' )
. With ( ProfileEndCallbackFunctionPointer )
. SendToCompiler ( ) ;
#endif
}
2023-05-07 18:43:11 -04:00
#if UNITY_EDITOR
2023-03-28 13:24:16 -04:00
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
2023-05-07 18:43:11 -04:00
/// <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
2023-03-28 13:24:16 -04:00
{
// 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
}
}