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) |