200 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			200 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # SkipLocalsInit attribute
 | |
| 
 | |
| Use [`SkipLocalsInitAttribute`](xref:Unity.Burst.CompilerServices.SkipLocalsInitAttribute) to tell Burst that any stack allocations within a method don't have to be initialized to zero.
 | |
| 
 | |
| In C# all local variables are initialized to zero by default, which prevents a class of bugs related to undefined data. But this can impact runtime performance because initializing this data to zero takes work:
 | |
| 
 | |
| ```c#
 | |
| static unsafe int DoSomethingWithLUT(int* data);
 | |
| 
 | |
| static unsafe int DoSomething(int size)
 | |
| {
 | |
|     int* data = stackalloc int[size];
 | |
| 
 | |
|     // Initialize every field of data to be an incrementing set of values.
 | |
|     for (int i = 0; i < size; i++)
 | |
|     {
 | |
|         data[i] = i;
 | |
|     }
 | |
| 
 | |
|     // Use the data elsewhere.
 | |
|     return DoSomethingWithLUT(data);
 | |
| }
 | |
| ```
 | |
| 
 | |
| The X86 assembly for this is:
 | |
| 
 | |
| ```x86asm
 | |
|         push    rbp
 | |
|         .seh_pushreg rbp
 | |
|         push    rsi
 | |
|         .seh_pushreg rsi
 | |
|         push    rdi
 | |
|         .seh_pushreg rdi
 | |
|         mov     rbp, rsp
 | |
|         .seh_setframe rbp, 0
 | |
|         .seh_endprologue
 | |
|         mov     edi, ecx
 | |
|         lea     r8d, [4*rdi]
 | |
|         lea     rax, [r8 + 15]
 | |
|         and     rax, -16
 | |
|         movabs  r11, offset __chkstk
 | |
|         call    r11
 | |
|         sub     rsp, rax
 | |
|         mov     rsi, rsp
 | |
|         sub     rsp, 32
 | |
|         movabs  rax, offset burst.memset.inline.X64_SSE4.i32@@32
 | |
|         mov     rcx, rsi
 | |
|         xor     edx, edx
 | |
|         xor     r9d, r9d
 | |
|         call    rax
 | |
|         add     rsp, 32
 | |
|         test    edi, edi
 | |
|         jle     .LBB0_7
 | |
|         mov     eax, edi
 | |
|         cmp     edi, 8
 | |
|         jae     .LBB0_3
 | |
|         xor     ecx, ecx
 | |
|         jmp     .LBB0_6
 | |
| .LBB0_3:
 | |
|         mov     ecx, eax
 | |
|         and     ecx, -8
 | |
|         movabs  rdx, offset __xmm@00000003000000020000000100000000
 | |
|         movdqa  xmm0, xmmword ptr [rdx]
 | |
|         mov     rdx, rsi
 | |
|         add     rdx, 16
 | |
|         movabs  rdi, offset __xmm@00000004000000040000000400000004
 | |
|         movdqa  xmm1, xmmword ptr [rdi]
 | |
|         movabs  rdi, offset __xmm@00000008000000080000000800000008
 | |
|         movdqa  xmm2, xmmword ptr [rdi]
 | |
|         mov     rdi, rcx
 | |
|         .p2align        4, 0x90
 | |
| .LBB0_4:
 | |
|         movdqa  xmm3, xmm0
 | |
|         paddd   xmm3, xmm1
 | |
|         movdqu  xmmword ptr [rdx - 16], xmm0
 | |
|         movdqu  xmmword ptr [rdx], xmm3
 | |
|         paddd   xmm0, xmm2
 | |
|         add     rdx, 32
 | |
|         add     rdi, -8
 | |
|         jne     .LBB0_4
 | |
|         cmp     rcx, rax
 | |
|         je      .LBB0_7
 | |
|         .p2align        4, 0x90
 | |
| .LBB0_6:
 | |
|         mov     dword ptr [rsi + 4*rcx], ecx
 | |
|         inc     rcx
 | |
|         cmp     rax, rcx
 | |
|         jne     .LBB0_6
 | |
| .LBB0_7:
 | |
|         sub     rsp, 32
 | |
|         movabs  rax, offset "DoSomethingWithLUT"
 | |
|         mov     rcx, rsi
 | |
|         call    rax
 | |
|         nop
 | |
|         mov     rsp, rbp
 | |
|         pop     rdi
 | |
|         pop     rsi
 | |
|         pop     rbp
 | |
|         ret
 | |
| ```
 | |
| 
 | |
| In this example, the `movabs  rax, offset burst.memset.inline.X64_SSE4.i32@@32` line means that you've had to inject a memset to zero out the data. In the above example, you know that the array is entirely initialized in the following loop, but Burst doesn't know that. 
 | |
| 
 | |
| To fix this problem, use [`Unity.Burst.CompilerServices.SkipLocalsInitAttribute`](xref:Unity.Burst.CompilerServices.SkipLocalsInitAttribute), which tells Burst that any stack allocations within a method don't have to be initialized to zero. 
 | |
| 
 | |
| >[!NOTE]
 | |
| >Only use this attribute if you're certain that you won't run into undefined behavior bugs. 
 | |
| 
 | |
| For example:
 | |
| 
 | |
| ```c#
 | |
| using Unity.Burst.CompilerServices;
 | |
| 
 | |
| static unsafe int DoSomethingWithLUT(int* data);
 | |
| 
 | |
| [SkipLocalsInit]
 | |
| static unsafe int DoSomething(int size)
 | |
| {
 | |
|     int* data = stackalloc int[size];
 | |
| 
 | |
|     // Initialize every field of data to be an incrementing set of values.
 | |
|     for (int i = 0; i < size; i++)
 | |
|     {
 | |
|         data[i] = i;
 | |
|     }
 | |
| 
 | |
|     // Use the data elsewhere.
 | |
|     return DoSomethingWithLUT(data);
 | |
| }
 | |
| ```
 | |
| 
 | |
| The assembly after adding the `[SkipLocalsInit]` on the method is:
 | |
| 
 | |
| ```x86asm
 | |
|         push    rbp
 | |
|         .seh_pushreg rbp
 | |
|         mov     rbp, rsp
 | |
|         .seh_setframe rbp, 0
 | |
|         .seh_endprologue
 | |
|         mov     edx, ecx
 | |
|         lea     eax, [4*rdx]
 | |
|         add     rax, 15
 | |
|         and     rax, -16
 | |
|         movabs  r11, offset __chkstk
 | |
|         call    r11
 | |
|         sub     rsp, rax
 | |
|         mov     rcx, rsp
 | |
|         test    edx, edx
 | |
|         jle     .LBB0_7
 | |
|         mov     r8d, edx
 | |
|         cmp     edx, 8
 | |
|         jae     .LBB0_3
 | |
|         xor     r10d, r10d
 | |
|         jmp     .LBB0_6
 | |
| .LBB0_3:
 | |
|         mov     r10d, r8d
 | |
|         and     r10d, -8
 | |
|         movabs  rax, offset __xmm@00000003000000020000000100000000
 | |
|         movdqa  xmm0, xmmword ptr [rax]
 | |
|         mov     rax, rcx
 | |
|         add     rax, 16
 | |
|         movabs  rdx, offset __xmm@00000004000000040000000400000004
 | |
|         movdqa  xmm1, xmmword ptr [rdx]
 | |
|         movabs  rdx, offset __xmm@00000008000000080000000800000008
 | |
|         movdqa  xmm2, xmmword ptr [rdx]
 | |
|         mov     r9, r10
 | |
|         .p2align        4, 0x90
 | |
| .LBB0_4:
 | |
|         movdqa  xmm3, xmm0
 | |
|         paddd   xmm3, xmm1
 | |
|         movdqu  xmmword ptr [rax - 16], xmm0
 | |
|         movdqu  xmmword ptr [rax], xmm3
 | |
|         paddd   xmm0, xmm2
 | |
|         add     rax, 32
 | |
|         add     r9, -8
 | |
|         jne     .LBB0_4
 | |
|         cmp     r10, r8
 | |
|         je      .LBB0_7
 | |
|         .p2align        4, 0x90
 | |
| .LBB0_6:
 | |
|         mov     dword ptr [rcx + 4*r10], r10d
 | |
|         inc     r10
 | |
|         cmp     r8, r10
 | |
|         jne     .LBB0_6
 | |
| .LBB0_7:
 | |
|         sub     rsp, 32
 | |
|         movabs  rax, offset "DoSomethingWithLUT"
 | |
|         call    rax
 | |
|         nop
 | |
|         mov     rsp, rbp
 | |
|         pop     rbp
 | |
|         ret
 | |
| ```
 | |
| 
 | |
| The call to memset is now gone, because you've told Burst that any stack allocations within a method don't have to be initialized to zero. 
 | |
| 
 | |
| ## Additional resources
 | |
| 
 | |
| * [`[SkipLocalsInitAttribute]` API reference](xref:Unity.Burst.CompilerServices.SkipLocalsInitAttribute)
 |