Documentation

Unity Engine


User Manual

Script Reference

Unity Engine


Declaring non-aliasing pointers and structs

Read time 7 minutesLast updated 10 days ago

Use the
[NoAlias]
attribute to give Burst additional information on the aliasing of pointers and structs.
In most use cases, you won't need to use the
[NoAlias]
attribute. You don't need to apply it to a struct definition that already has a
[NativeContainer]
attribute or to fields in job structs because in these cases Burst infers the no-alias information.
The
[NoAlias]
attribute is exposed so that you can construct complex data structures where Burst can't infer the aliasing. If you use the
[NoAlias]
attribute on a pointer that could alias with another, it might result in undefined behavior and make it hard to track down bugs.
You can use this attribute in the following ways:
  • On a function parameter it signifies that the parameter doesn't alias with any other parameter to the function.
  • On a struct field it signifies that the field doesn't alias with any other
    [NoAlias]
    field of the struct.
  • On a struct it signifies that the address of the struct can't appear within the struct itself.
  • On a function return value it signifies that the returned pointer doesn't alias with any other pointer returned from the same function.

NoAlias function parameter

The following is an example of aliasing:
int Foo(ref int a, ref int b){ b = 13; a = 42; return b;}
For this, Burst produces the following assembly:
mov dword ptr [rdx], 13mov dword ptr [rcx], 42mov eax, dword ptr [rdx]ret
This means that Burst does the following:
  • Stores 13 into
    b
    .
  • Stores 42 into
    a
    .
  • Reloads the value from
    b
    to return it.
Burst has to reload
b
because it doesn't know whether
a
and
b
are backed by the same memory or not.
Add the
[NoAlias]
attribute to the code to change this:
int Foo([NoAlias] ref int a, ref int b){ b = 13; a = 42; return b;}
For this, Burst produces the following assembly:
mov dword ptr [rdx], 13mov dword ptr [rcx], 42mov eax, 13ret
In this case, the load from
b
has been replaced with moving the constant 13 into the return register.

NoAlias struct field

The following example is the same as the previous, but applied to a struct:
struct Bar{ public NativeArray<int> a; public NativeArray<float> b;}int Foo(ref Bar bar){ bar.b[0] = 42.0f; bar.a[0] = 13; return (int)bar.b[0];}
For this, Burst produces the following assembly:
mov rax, qword ptr [rcx + 16]mov dword ptr [rax], 1109917696mov rcx, qword ptr [rcx]mov dword ptr [rcx], 13cvttss2si eax, dword ptr [rax]ret
In this case, Burst does the following:
  • Loads the address of the data in
    b
    into
    rax
    .
  • Stores 42 into it (
    1109917696
    is
    0x42280000
    , which is
    42.0f
    ).
  • Loads the address of the data in
    a
    into
    rcx
    .
  • Stores 13 into it.
  • Reloads the data in
    b
    and converts it to an integer for returning.
If you know that the two
NativeArrays
aren't backed by the same memory, you can change the code to the following:
struct Bar{ [NoAlias] public NativeArray<int> a; [NoAlias] public NativeArray<float> b;}int Foo(ref Bar bar){ bar.b[0] = 42.0f; bar.a[0] = 13; return (int)bar.b[0];}
If you attribute both
a
and
b
with
[NoAlias]
it tells Burst that they don't alias with each other within the struct, which produces the following assembly:
mov rax, qword ptr [rcx + 16]mov dword ptr [rax], 1109917696mov rax, qword ptr [rcx]mov dword ptr [rax], 13mov eax, 42ret
This means that Burst can return the integer constant 42.

NoAlias struct

Burst assumes that the pointer to a struct doesn't appear within the struct itself. However, there are cases where this isn't true:
unsafe struct CircularList{ public CircularList* next; public CircularList() { // The 'empty' list just points to itself. next = this; }}
Lists are one of the few structures where it's normal to have the pointer to the struct accessible from somewhere within the struct itself.
The following example indicates where
[NoAlias]
on a struct can help:
unsafe struct Bar{ public int i; public void* p;}float Foo(ref Bar bar){ *(int*)bar.p = 42; return ((float*)bar.p)[bar.i];}
This produces the following assembly:
mov rax, qword ptr [rcx + 8]mov dword ptr [rax], 42mov rax, qword ptr [rcx + 8]mov ecx, dword ptr [rcx]movss xmm0, dword ptr [rax + 4*rcx]ret
In this case, Burst:
  • Loads
    p
    into
    rax
    .
  • Stores 42 into
    p
    .
  • Loads
    p
    into
    rax
    again.
  • Loads
    i
    into
    ecx
    .
  • Returns the index into
    p
    by
    i
    .
In this situation, Burst loads
p
twice. This is because it doesn't know if
p
points to the address of the struct
bar
. Once it stores 42 into
p
it has to reload the address of
p
from
bar
, which is a costly operation.
Add
[NoAlias]
to prevent this:
[NoAlias]unsafe struct Bar{ public int i; public void* p;}float Foo(ref Bar bar){ *(int*)bar.p = 42; return ((float*)bar.p)[bar.i];}
This produces the following assembly:
mov rax, qword ptr [rcx + 8]mov dword ptr [rax], 42mov ecx, dword ptr [rcx]movss xmm0, dword ptr [rax + 4*rcx]ret
In this situation, Burst only loads the address of
p
once, because
[NoAlias]
tells it that
p
can't be the pointer to
bar
.

NoAlias function return

Some functions can only return a unique pointer. For instance,
malloc
only returns a unique pointer. In this case,
[return:NoAlias]
gives some useful information to Burst.
Important
Only use
[return: NoAlias]
on functions that are guaranteed to produce a unique pointer. For example, with bump-allocations, or with things like
malloc
. Burst aggressively inlines functions for performance considerations, so with small functions, Burst inlines them into their parents to produce the same result without the attribute.
The following example uses a bump allocator backed with a stack allocation:
// Only ever returns a unique address into the stackalloc'ed memory.// We've made this no-inline because Burst will always try and inline// small functions like these, which would defeat the purpose of this// example[MethodImpl(MethodImplOptions.NoInlining)]unsafe int* BumpAlloc(int* alloca){ int location = alloca[0]++; return alloca + location;}unsafe int Func(){ int* alloca = stackalloc int[128]; // Store our size at the start of the alloca. alloca[0] = 1; int* ptr1 = BumpAlloc(alloca); int* ptr2 = BumpAlloc(alloca); *ptr1 = 42; *ptr2 = 13; return *ptr1;}
This produces the following assembly:
push rsipush rdipush rbxsub rsp, 544lea rcx, [rsp + 36]movabs rax, offset memsetmov r8d, 508xor edx, edxcall raxmov dword ptr [rsp + 32], 1movabs rbx, offset "BumpAlloc(int* alloca)"lea rsi, [rsp + 32]mov rcx, rsicall rbxmov rdi, raxmov rcx, rsicall rbxmov dword ptr [rdi], 42mov dword ptr [rax], 13mov eax, dword ptr [rdi]add rsp, 544pop rbxpop rdipop rsiret
The key things that Burst does:
  • Has
    ptr1
    in
    rdi
    .
  • Has
    ptr2
    in
    rax
    .
  • Stores 42 into
    ptr1
    .
  • Stores 13 into
    ptr2
    .
  • Loads
    ptr1
    again to return it.
If you add the
[return: NoAlias]
attribute:
[MethodImpl(MethodImplOptions.NoInlining)][return: NoAlias]unsafe int* BumpAlloc(int* alloca){ int location = alloca[0]++; return alloca + location;}unsafe int Func(){ int* alloca = stackalloc int[128]; // Store our size at the start of the alloca. alloca[0] = 1; int* ptr1 = BumpAlloc(alloca); int* ptr2 = BumpAlloc(alloca); *ptr1 = 42; *ptr2 = 13; return *ptr1;}
It produces the following assembly:
push rsipush rdipush rbxsub rsp, 544lea rcx, [rsp + 36]movabs rax, offset memsetmov r8d, 508xor edx, edxcall raxmov dword ptr [rsp + 32], 1movabs rbx, offset "BumpAlloc(int* alloca)"lea rsi, [rsp + 32]mov rcx, rsicall rbxmov rdi, raxmov rcx, rsicall rbxmov dword ptr [rdi], 42mov dword ptr [rax], 13mov eax, 42add rsp, 544pop rbxpop rdipop rsiret
In this case, Burst doesn't reload
ptr2
, and moves 42 into the return register.

Additional resources