Library -Artifacts
11 KiB
Language Support
Burst supports most of the expressions and statements:
- Regular C# control flows:
if
/else
/switch
/case
/for
/while
/break
/continue
.
- Extension methods.
- Unsafe code, pointer manipulation...etc.
- Instance methods of structs.
- By ref/out parameters.
DllImport
and internal calls.- Limited support for
throw
expressions, assuming simple throw patterns (e.gthrow new ArgumentException("Invalid argument")
). In that case, we will try to extract the static string exception message to include it in the generated code. - Some special IL opcodes like
cpblk
,initblk
,sizeof
. - Loading from static readonly fields.
- Support for
fixed
statements. - Support for
try
/finally
and associated IDisposable patterns (using
andforeach
)- Note that if an exception occurs, the behavior will differ from .NET. In .NET, if an exception occurs inside a
try
block, control flow would go to thefinally
block. In Burst, if an exception occurs whether inside or outside atry
block, the currently running job or function pointer will terminate immediately.
- Note that if an exception occurs, the behavior will differ from .NET. In .NET, if an exception occurs inside a
- Partial support for strings and
Debug.Log
. - Support for strings and
ProfilerMarker
.
Burst also provides alternatives for some C# constructions not directly accessible to HPC#:
- Function pointers as an alternative to using delegates within HPC#
- Shared Static to access static mutable data from both C# and HPC#
Burst does not support:
- Catching exceptions
catch
in atry
/catch
. - Storing to static fields except via Shared Static
- Any methods related to managed objects (e.g string methods...etc.)
Static readonly fields and static constructors
Burst will try to evaluate all static fields and all static constructors at compile-time. The same base language is supported at compile-time, with some exceptions:
- Managed arrays and strings are supported.
- Calling external functions and function pointers is not supported.
- Only some intrinsics are supported:
- Assertions:
UnityEngine.Debug.Assert
NUnit.Framework.Assert.AreEqual
NUnit.Framework.Assert.AreNotEqual
Unity.Burst.BurstCompiler.IsEnabled
Unity.Burst.BurstRuntime.GetHashCode32
andUnity.Burst.BurstRuntime.GetHashCode64
- Vector type construction
- Assertions:
Simple throw patterns are also supported. Exceptions thrown during evaluation become compiler errors.
All the static fields and the static constructor for a given struct are evaluated together. Having non-readonly static fields in a Burst compiled struct will therefore cause a compilation error, since only readonly static fields are supported. If any static field or the static constructor fails to evaluate then all of them fail to evaluate for that struct. When compile-time evaluation fails Burst will try to fall back to compiling all static initialization code into an initialization function to be called once at runtime. This means that if code fails compile time evaluation it needs to be Burst compatible, otherwise compilation will fail. An exception to this is that there's still limited support for initializing static readonly array fields as long as they are initialized from either an array constructor or from static data:
static readonly int[] MyArray0 = { 1, 2, 3, .. };
static readonly int[] MyArray1 = new int[10];
Directly Calling Burst Compiled Code
Burst supports the ability to call out to Burst compiled methods directly from managed code (2019.4+
only). Calling generic methods or methods whose declaring type is generic is not supported, otherwise the rules as for Function pointers apply. However as a user you don't need to worry about the extra boiler plate needed for function pointers.
As an example here is a utility class which is Burst compiled (notice because we are using structs we are passing by reference as per the Function Pointer rules).
[BurstCompile]
public static class MyBurstUtilityClass
{
[BurstCompile]
public static void BurstCompiled_MultiplyAdd(in float4 mula, in float4 mulb, in float4 add, out float4 result)
{
result = mula * mulb + add;
}
}
Now we can utilise this method from our Managed code e.g.
public class MyMonoBehaviour : MonoBehaviour
{
void Start()
{
var mula = new float4(1, 2, 3, 4);
var mulb = new float4(-1,1,-1,1);
var add = new float4(99,0,0,0);
MyBurstUtilityClass.BurstCompiled_MultiplyAdd(mula, mulb, add, out var result);
Debug.Log(result);
}
}
and if we were to run (with the managed script attached to an object), we should now see in the log float4(98f, 2f, -3f, 4f)
.
This is achieved by using IL Post Processing in order to automatically transform the code into a function pointer and call, which is why you must adhere to the rules of Function pointers.
You can disable the direct call transformation by adding DisableDirectCall = true
to the BurstCompile options, which will prevent the Post Processor from running on the code. E.g.
[BurstCompile]
public static class MyBurstUtilityClass
{
[BurstCompile(DisableDirectCall = true)]
public static void BurstCompiled_MultiplyAdd(in float4 mula, in float4 mulb, in float4 add, out float4 result)
{
result = mula * mulb + add;
}
}
Throw and Exceptions
Burst supports throw
expressions for exceptions in the Editor (2019.3+
only), but crucially does not in Standalone Player builds. Exceptions in Burst are to be used solely for exceptional behavior. To ensure that code does not end up relying on exceptions for things like general control flow, Burst will produce the following warning on code that tries to throw
within a method not attributed by [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
:
Burst warning BC1370: An exception was thrown from a function without the correct [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] guard. Exceptions only work in the editor and so should be protected by this guard
Partial support for strings and Debug.Log
Burst provides partial support for using strings in the following two main scenarios:
Debug.Log
(see below)- Assignment of a string to various FixedString structs provided by
Unity.Collections
(e.gFixedString128
)
A string can be either:
- A string literal (e.g
"This is a string literal"
). - An interpolated string (using
$"This is an integer {value}
or usingstring.Format
) where the string to format is also a string literal
For example, the following constructions are supported:
- Logging with a string literal:
Debug.Log("This a string literal");
- Logging using string interpolation:
int value = 256;
Debug.Log($"This is an integer value {value}");
Which is equivalent to using string.Format
directly:
int value = 256;
Debug.Log(string.Format("This is an integer value {0}", value));
While it is possible to pass a managed string
literal or an interpolated string directly to Debug.Log
, it is not possible to pass a string to a user method or to use them as fields in a struct. In order to pass or store strings around, you need to use one of the FixedString
structs provided by the Unity.Collections
package:
int value = 256;
FixedString128 text = $"This is an integer value {value} used with FixedString128";
MyCustomLog(text);
// ...
// String can be passed as an argument to a method using a FixedString,
// but not using directly a managed `string`:
public static void MyCustomLog(in FixedString128 log)
{
Debug.Log(text);
}
Burst has limited support for string format arguments and specifiers:
int value = 256;
// Padding left: "This value ` 256`
Debug.Log($"This value `{value,5}`");
// Padding right: "This value `256 `
Debug.Log($"This value `{value,-5}`");
// Hexadecimal uppercase: "This value `00FF`
Debug.Log($"This value `{value:X4}`");
// Hexadecimal lowercase: "This value `00ff`
Debug.Log($"This value `{value:x4}`");
// Decimal with leading-zero: "This value `0256`
Debug.Log($"This value `{value:D4}`");
What is supported currently:
- The following
Debug.Log
methods:Debug.Log(object)
Debug.LogWarning(object)
Debug.LogError(object)
- String interpolation is working with the following caveats:
- The string to format must be a literal.
- Only the following
string.Format
methods are supported:string.Format(string, object)
,string.Format(string, object, object)
,string.Format(string, object, object, object)
and more if the .NET API provides specializations with object arguments.string.Format(string, object[])
: which can happen for a string interpolation that would contain more than 3 arguments (e.g$"{arg1} {arg2} {arg3} {arg4} {arg5}..."
). In that case, we expect the object[] array to be of a constant size and no arguments should involve control flows (e.g$"This is a {(cond ? arg1 : arg2)}"
)
- Only value types.
- Only primitive type arguments:
char
,boolean
,byte
,sbyte
,ushort
,short
,uint
,int
,ulong
,long
,float
,double
. - All vector types e.g
int2
,float3
are supported, excepthalf
vector types.var value = new float3(1.0f, 2.0f, 3.0f); // Logs "This value float3(1f, 2f, 3f)" Debug.Log($"This value `{value}`");
- Does not support
ToString()
of structs. It will display the full name of the struct instead.
- The supported string format specifiers are only
G
,g
,D
,d
andX
,x
, with padding and specifier. For more details:
You can also use System.Runtime.CompilerServices
attributes [CallerLineNumber]
, [CallerMemberName]
, and [CallerFilePath]
on arguments to Burst functions, but the strings can only be passed directly to calls to Debug.Log
at present.
Support for strings and ProfilerMarker
In Unity 2020.2 and above, you can call new ProfilerMarker("MarkerName")
from Burst code:
[BurstCompile]
private static class ProfilerMarkerWrapper
{
private static readonly ProfilerMarker StaticMarker = new ProfilerMarker("TestStaticBurst");
[BurstCompile(CompileSynchronously = true)]
public static int CreateAndUseProfilerMarker(int start)
{
using (StaticMarker.Auto())
{
var p = new ProfilerMarker("TestBurst");
p.Begin();
var result = 0;
for (var i = start; i < start + 100000; i++)
{
result += i;
}
p.End();
return result;
}
}
}