362 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			362 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # NoAlias attribute
 | |
| 
 | |
| Use the [`[NoAlias]`](xref:Unity.Burst.NoAliasAttribute) 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]`](xref:Unity.Collections.LowLevel.Unsafe.NativeContainerAttribute) 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:
 | |
| 
 | |
| ```c#
 | |
| int Foo(ref int a, ref int b)
 | |
| {
 | |
|     b = 13;
 | |
|     a = 42;
 | |
|     return b;
 | |
| }
 | |
| ```
 | |
| 
 | |
| For this, Burst produces the following assembly:
 | |
| 
 | |
| ```x86asm
 | |
| mov     dword ptr [rdx], 13
 | |
| mov     dword ptr [rcx], 42
 | |
| mov     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:
 | |
| 
 | |
| ```c#
 | |
| int Foo([NoAlias] ref int a, ref int b)
 | |
| {
 | |
|     b = 13;
 | |
|     a = 42;
 | |
|     return b;
 | |
| }
 | |
| ```
 | |
| 
 | |
| For this, Burst produces the following assembly:
 | |
| 
 | |
| ```x86asm
 | |
| mov     dword ptr [rdx], 13
 | |
| mov     dword ptr [rcx], 42
 | |
| mov     eax, 13
 | |
| ret
 | |
| ```
 | |
| 
 | |
| 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:
 | |
| 
 | |
| ```c#
 | |
| 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:
 | |
| 
 | |
| ```x86asm
 | |
| mov     rax, qword ptr [rcx + 16]
 | |
| mov     dword ptr [rax], 1109917696
 | |
| mov     rcx, qword ptr [rcx]
 | |
| mov     dword ptr [rcx], 13
 | |
| cvttss2si       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:
 | |
| 
 | |
| ```c#
 | |
| 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:
 | |
| 
 | |
| ```x86asm
 | |
| mov     rax, qword ptr [rcx + 16]
 | |
| mov     dword ptr [rax], 1109917696
 | |
| mov     rax, qword ptr [rcx]
 | |
| mov     dword ptr [rax], 13
 | |
| mov     eax, 42
 | |
| ret
 | |
| ```
 | |
| 
 | |
| 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:
 | |
| 
 | |
| ```c#
 | |
| 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:
 | |
| 
 | |
| ```c#
 | |
| 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:
 | |
| 
 | |
| ```x86asm
 | |
| mov     rax, qword ptr [rcx + 8]
 | |
| mov     dword ptr [rax], 42
 | |
| mov     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:
 | |
| 
 | |
| ```c#
 | |
| [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:
 | |
| 
 | |
| ```x86asm
 | |
| mov     rax, qword ptr [rcx + 8]
 | |
| mov     dword ptr [rax], 42
 | |
| mov     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:
 | |
| 
 | |
| ```c#
 | |
| // 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:
 | |
| 
 | |
| ```x86asm
 | |
| push    rsi
 | |
| push    rdi
 | |
| push    rbx
 | |
| sub     rsp, 544
 | |
| lea     rcx, [rsp + 36]
 | |
| movabs  rax, offset memset
 | |
| mov     r8d, 508
 | |
| xor     edx, edx
 | |
| call    rax
 | |
| mov     dword ptr [rsp + 32], 1
 | |
| movabs  rbx, offset "BumpAlloc(int* alloca)"
 | |
| lea     rsi, [rsp + 32]
 | |
| mov     rcx, rsi
 | |
| call    rbx
 | |
| mov     rdi, rax
 | |
| mov     rcx, rsi
 | |
| call    rbx
 | |
| mov     dword ptr [rdi], 42
 | |
| mov     dword ptr [rax], 13
 | |
| mov     eax, dword ptr [rdi]
 | |
| add     rsp, 544
 | |
| pop     rbx
 | |
| pop     rdi
 | |
| pop     rsi
 | |
| ret
 | |
| ```
 | |
| 
 | |
| 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:
 | |
| 
 | |
| ```c#
 | |
| [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:
 | |
| 
 | |
| ```x86asm
 | |
| push    rsi
 | |
| push    rdi
 | |
| push    rbx
 | |
| sub     rsp, 544
 | |
| lea     rcx, [rsp + 36]
 | |
| movabs  rax, offset memset
 | |
| mov     r8d, 508
 | |
| xor     edx, edx
 | |
| call    rax
 | |
| mov     dword ptr [rsp + 32], 1
 | |
| movabs  rbx, offset "BumpAlloc(int* alloca)"
 | |
| lea     rsi, [rsp + 32]
 | |
| mov     rcx, rsi
 | |
| call    rbx
 | |
| mov     rdi, rax
 | |
| mov     rcx, rsi
 | |
| call    rbx
 | |
| mov     dword ptr [rdi], 42
 | |
| mov     dword ptr [rax], 13
 | |
| mov     eax, 42
 | |
| add     rsp, 544
 | |
| pop     rbx
 | |
| pop     rdi
 | |
| pop     rsi
 | |
| ret
 | |
| ```
 | |
| 
 | |
| In this case, Burst doesn't reload `ptr2`, and moves 42 into the return register.
 | |
| 
 | |
| ## Additional resources
 | |
| 
 | |
| * [Memory aliasing](aliasing.md)
 | |
| * [Aliasing and the job system](aliasing-job-system.md) |