Skip to content

Jit double zeros #2325

Closed
Closed

Description

@benaadams

Given sharplab

using System.Buffers;
using System.Runtime.CompilerServices;

public class C 
{
    public void M() 
    {
        var amb = AsyncValueTaskMethodBuilder<ReadResult>.Create();
    }
}

public readonly struct ReadResult
{
    internal readonly ReadOnlySequence<byte> _resultBuffer;
    internal readonly int _resultFlags;
}

The Jit zeros in the preamble with rep stosd and then zeros in the body with vmovdqu

L0000: push rdi
L0001: push rsi
L0002: sub rsp, 0x38
L0006: vzeroupper
L0009: mov rsi, rcx
L000c: lea rdi, [rsp+0x8]           ;
L0011: mov ecx, 0xc                 ;
L0016: xor eax, eax                 ;
L0018: rep stosd                    ; clear stack
L001a: mov rcx, rsi
L001d: lea rax, [rsp+0x8]           ;
L0022: vxorps xmm0, xmm0, xmm0      ; 
L0026: vmovdqu [rax], xmm0          ; 
L002a: vmovdqu [rax+0x10], xmm0     ; 
L002f: vmovdqu [rax+0x20], xmm0     ; clear stack again
L0034: add rsp, 0x38
L0038: pop rsi
L0039: pop rdi
L003a: ret

Would it be possible for the Jit to skip the second set of zeroing given its already zeroed the stack at the start?

category:cq
theme:prolog-epilog
skill-level:expert
cost:medium

Activity

  1. added
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    untriagedNew issue has not been triaged by the area owner
    on Jan 29, 2020
  2. changed the title [-]Jit over zeros[/-] [+]Jit double zeros[/+] on Jan 29, 2020
  3. removed
    untriagedNew issue has not been triaged by the area owner
    on Jan 29, 2020
  4. AndyAyersMS commented on Jan 29, 2020

    @AndyAyersMS
    Member
  5. benaadams commented on Jan 29, 2020

    @benaadams
    MemberAuthor

    A more specific/common example is async, for example ReadResults in System.IO.Pipelines; which may be more problematic as currently the C# statemachine generation inserts the saving of parameters before the call to .Create and assign, which does the second zeroing.

    using System.IO.Pipelines;
    
    public async ValueTask<ReadResult> OuterMethod(object o)
    {
        return await InnerMethod(o);
        
        static async ValueTask<ReadResult> InnerMethod(object o) 
        {
            return default;
        }
    }
    G_M16490_IG01:
        push     rdi
        push     rsi
        sub      rsp, 136
        vzeroupper 
        mov      rsi, rcx                 ;
        lea      rdi, [rsp+20H]           ;
        mov      ecx, 26                  ;
        xor      rax, rax                 ;
        rep stosd                         ; zero
        mov      rcx, rsi
        mov      rsi, rdx
    						;; bbWeight=1    PerfScore 30.00
    G_M16490_IG02:
        mov      gword ptr [rsp+20H], r8          ; save param
        xor      ecx, ecx                         ; re-zero
        vxorps   xmm0, xmm0                       ; re-zero
        vmovdqu  xmmword ptr [rsp+30H], xmm0      ; re-zero
        vmovdqu  xmmword ptr [rsp+40H], xmm0      ; re-zero
        mov      qword ptr [rsp+50H], rcx         ; re-zero
        mov      dword ptr [rsp+28H], -1
        lea      rcx, [rsp+20H]
        call     System.Runtime.CompilerServices.AsyncMethodBuilderCore:Start(byref)
        lea      rcx, bword ptr [rsp+30H]
        mov      rdx, rsi
        call     System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder`1[ReadResult][System.IO.Pipelines.ReadResult]:get_Task():System.Threading.Tasks.ValueTask`1[ReadResult]:this
        mov      rax, rsi
    						;; bbWeight=1    PerfScore 9.08
    G_M16490_IG03:
        add      rsp, 136
        pop      rsi
        pop      rdi
        ret      
    						;; bbWeight=1    PerfScore 2.25
  6. erozenfeld commented on Jan 29, 2020

    @erozenfeld
    Contributor

    I'll investigate.

  7. self-assigned this
    on Jan 29, 2020
  8. erozenfeld commented on Feb 3, 2020

    @erozenfeld
    Contributor

    #31677 reduces the code for the first example to a single ret instruction.

    The second one is harder to deal with. We are very conservative with address-exposed locals and don't try to figure out any liveness information for them. I'm trying to find an inexpensive way to do better at least in some simple cases.

  9. benaadams commented on Feb 3, 2020

    @benaadams
    MemberAuthor

    @erozenfeld would this roslyn change help, or not make any difference? dotnet/roslyn#41253 (comment)

  10. benaadams commented on Feb 4, 2020

    @benaadams
    MemberAuthor

    Or would changing it to move all the zeroing above that first assignment to non-zero (which would be a much more complex refactoring in Roslyn)

  11. erozenfeld commented on Feb 4, 2020

    @erozenfeld
    Contributor

    Neither of these two changes will help the jit. It has trouble with double initialization in much simpler cases, e.g., #1007. In your example there are 3 blockers for the jit. Any of them will currently result in double zero-initialization.

    1. The local is marked as address-exposed and the jit doesn't do any liveness tracking for such locals.
      Here the explicit initialization happens in the first basic block so maybe we can special-case this somehow.
    2. We are initializing a field of struct type. There is some unfinished work on better morphing such fields that could help here, e.g.,
      // TODO-ADDR: Add TYP_STRUCT support to LCL_FLD.
    3. The struct has gc fields. We are very conservative in initializing such structs in the prolog:
      if ((!varDsc->lvTracked || (varDsc->lvType == TYP_STRUCT)) && varDsc->lvOnFrame &&
      (!varDsc->lvIsTemp || varDsc->HasGCPtr()))
      {
      varDsc->lvMustInit = true;
  12. benaadams commented on Feb 4, 2020

    @benaadams
    MemberAuthor

    Am wondering given 3. (prolog initialization); then specialising the first block might be a good route/stopgap?

    i.e. if its going to do rep stosd which has a high fixed cost; then its better to use its low variable cost to zero rather than try to anything else, and its probably a common case? e.g. All the examples in #1007 are first block and C# forces explicit initialization; as well as picking up every async Start method which all have this pattern.

    The first block can assume prolog initialization in certain circumstances e.g. (1.) (3.) above, and only needs to consider itself (e.g. given this type will be zero; has it been overwritten in my block, requiring re-zero); whereas I assume full tracking to eliminate double writes across the entire method would be a lot more involved?

    Obviously I'm just armchairing this and don't have any idea of the complexity to do that; so feel free to disregard 😉

  13. erozenfeld commented on Feb 4, 2020

    @erozenfeld
    Contributor

    The jit needs additional analysis in order to remove one of the zero initializations safely.

    To remove a zero-initialization of a struct with GC fields in the prolog the jit must make sure that the struct is initialized explicitly before any use (if initlocals is set on the method) and also before any GC-safe point. Note that initlocals is stripped in almost all framework assemblies, so that helps a bit.

    To remove an explicit zero-initialization of a struct with GC fields the jit must ensure that the struct is not written-to on any path between the prolog and the explicit initialization. It also needs to check that the explicit initialization is not in a loop.

    The jit doesn't currently have that information for address-exposed structs. I don't see a cheap and easy way to get the information. Yes, doing the analysis for explicit initializations in the first basic block may be a good compromise but even that is not straightforward to implement. In your case we are zero-initializing a field of type struct rather than a local struct, which is another complication for the jit. I'll keep exploring the possibilities here.

  14. 12 remaining items

  15. erozenfeld commented on May 23, 2020

    @erozenfeld
    Contributor

    @benaadams #36918 addresses
    DateTimeParse:ParseExact(ReadOnlySpan`1,ReadOnlySpan`1,DateTimeFormatInfo,int):DateTime
    case:

    G_M60355_IG01:
           sub      rsp, 184
           vzeroupper 
           xor      rax, rax
    -      mov      qword ptr [rsp+28H], rax
    +      mov      qword ptr [rsp+38H], rax
           vxorps   xmm4, xmm4
    -      vmovdqa  xmmword ptr [rsp+30H], xmm4
           vmovdqa  xmmword ptr [rsp+40H], xmm4
           mov      rax, -96
           vmovdqa  xmmword ptr [rsp+rax+B0H], xmm4
           vmovdqa  xmmword ptr [rsp+rax+C0H], xmm4
           vmovdqa  xmmword ptr [rsp+rax+D0H], xmm4
           add      rax, 48
           jne      SHORT  -5 instr
           mov      qword ptr [rsp+B0H], rax
    G_M60355_IG02:
    -      vxorps   xmm0, xmm0
    -      vmovdqu  xmmword ptr [rsp+38H], xmm0
    -      vmovdqu  xmmword ptr [rsp+48H], xmm0
    -      vmovdqu  xmmword ptr [rsp+58H], xmm0
    -      vmovdqu  xmmword ptr [rsp+68H], xmm0
    -      vmovdqu  xmmword ptr [rsp+78H], xmm0
    -      vmovdqu  xmmword ptr [rsp+88H], xmm0
    -      vmovdqu  xmmword ptr [rsp+98H], xmm0
    -      vmovdqu  xmmword ptr [rsp+A8H], xmm0
           mov      rax, bword ptr [rcx]
           mov      r10d, dword ptr [rcx+8]
           lea      r11, bword ptr [rsp+98H]
           mov      bword ptr [r11], rax
           mov      dword ptr [r11+8], r10d
           mov      dword ptr [rsp+60H], -1
           mov      dword ptr [rsp+64H], -1
           mov      dword ptr [rsp+68H], -1
           vmovsd   xmm0, qword ptr [reloc @RWD00]
           vmovsd   qword ptr [rsp+58H], xmm0
           mov      dword ptr [rsp+78H], -1
    
    G_M60355_IG03:
           vmovdqu  xmm0, xmmword ptr [rcx]
           vmovdqu  xmmword ptr [rsp+28H], xmm0
    						;; bbWeight=1    PerfScore 3.00
    G_M60355_IG04:
           lea      rcx, [rsp+38H]
           mov      qword ptr [rsp+20H], rcx
           lea      rcx, bword ptr [rsp+28H]
           call     DateTimeParse:TryParseExact(ReadOnlySpan`1,ReadOnlySpan`1,DateTimeFormatInfo,int,byref):bool
           test     eax, eax
           je       SHORT G_M60355_IG06
           mov      rax, qword ptr [rsp+90H]
    G_M60355_IG05:
           add      rsp, 184
           ret      
    G_M60355_IG06:
           lea      rcx, [rsp+38H]
           call     DateTimeParse:GetDateTimeParseException(byref):Exception
           mov      rcx, rax
           call     CORINFO_HELP_THROW
           int3     
    RWD00  dq	BFF0000000000000h
    
    
    -; Total bytes of code 282, prolog size 84, PerfScore 71.27, (MethodHash=de53143c) for method DateTimeParse:ParseExact(ReadOnlySpan`1,ReadOnlySpan`1,DateTimeFormatInfo,int):DateTime
    +; Total bytes of code 215, prolog size 78, PerfScore 54.23, (MethodHash=de53143c) for method DateTimeParse:ParseExact(ReadOnlySpan`1,ReadOnlySpan`1,DateTimeFormatInfo,int):DateTime
    
  16. erozenfeld commented on May 23, 2020

    @erozenfeld
    Contributor

    Regarding your second example where we first assign to a gc field and then initialize the struct field: currently my optimization doesn't handle that. It's possible to extend it to handle that case but that would require keeping track of field initializations and may be a little tricky.

    However, now the answer to your question

    Or would changing it to move all the zeroing above that first assignment to non-zero (which would be a much more complex refactoring in Roslyn) help?

    is yes.

    If the struct field zero initialization was done before the first assignment, it would be eliminated by the new optimization.

  17. benaadams commented on May 23, 2020

    @benaadams
    MemberAuthor

    💜 Thank you; sounds like this issue would be resolved then and the other fixed by a tweak in Roslyn (though that proved a bit hard when I looked; might give it another go though)

  18. benaadams commented on May 27, 2020

    @benaadams
    MemberAuthor

    Resolved by #36918

    Thank you @erozenfeld 🎉

  19. benaadams commented on Jun 18, 2020

    @benaadams
    MemberAuthor

    Looks like the tweak in Rosyn dotnet/roslyn#45262 doesn't enable the zero init optimization

    public async ValueTask<ReadResult> OuterMethod(object o)
    {
        return await InnerMethod(o);
    
    }
    
    static async ValueTask<ReadResult> InnerMethod(object o)
    {
        return default;
    }

    IL code if easier to work with

    .method public hidebysig 
    	instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<valuetype [System.IO.Pipelines]System.IO.Pipelines.ReadResult> OuterMethod (
    		object o
    	) cil managed 
    {
    	.custom instance void [System.Runtime]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [System.Runtime]System.Type) = (
    		01 00 13 43 2b 3c 4f 75 74 65 72 4d 65 74 68 6f
    		64 3e 64 5f 5f 31 00 00
    	)
    	// Method begins at RVA 0x2054
    	// Code size 55 (0x37)
    	.maxstack 2
    	.locals init (
    		[0] valuetype C/'<OuterMethod>d__1'
    	)
    
    	IL_0000: ldloca.s 0
    	IL_0002: call valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder`1<!0> valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder`1<valuetype [System.IO.Pipelines]System.IO.Pipelines.ReadResult>::Create()
    	IL_0007: stfld valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder`1<valuetype [System.IO.Pipelines]System.IO.Pipelines.ReadResult> C/'<OuterMethod>d__1'::'<>t__builder'
    	IL_000c: ldloca.s 0
    	IL_000e: ldarg.1
    	IL_000f: stfld object C/'<OuterMethod>d__1'::o
    	IL_0014: ldloca.s 0
    	IL_0016: ldc.i4.m1
    	IL_0017: stfld int32 C/'<OuterMethod>d__1'::'<>1__state'
    	IL_001c: ldloca.s 0
    	IL_001e: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder`1<valuetype [System.IO.Pipelines]System.IO.Pipelines.ReadResult> C/'<OuterMethod>d__1'::'<>t__builder'
    	IL_0023: ldloca.s 0
    	IL_0025: call instance void valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder`1<valuetype [System.IO.Pipelines]System.IO.Pipelines.ReadResult>::Start<valuetype C/'<OuterMethod>d__1'>(!!0&)
    	IL_002a: ldloca.s 0
    	IL_002c: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder`1<valuetype [System.IO.Pipelines]System.IO.Pipelines.ReadResult> C/'<OuterMethod>d__1'::'<>t__builder'
    	IL_0031: call instance valuetype [System.Runtime]System.Threading.Tasks.ValueTask`1<!0> valuetype [System.Runtime]System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder`1<valuetype [System.IO.Pipelines]System.IO.Pipelines.ReadResult>::get_Task()
    	IL_0036: ret
    } // end of method C::OuterMethod
    
    

    Output

    G_M51821_IG01:
           push     rsi
           sub      rsp, 144
           vzeroupper 
           xor      rax, rax
           mov      qword ptr [rsp+28H], rax
           vxorps   xmm4, xmm4
           mov      rax, -96
           vmovdqa  xmmword ptr [rsp+rax+90H], xmm4
           vmovdqa  xmmword ptr [rsp+rax+A0H], xmm4
           vmovdqa  xmmword ptr [rsp+rax+B0H], xmm4
           add      rax, 48
           jne      SHORT  -5 instr
           mov      rsi, rdx
    						;; bbWeight=1    PerfScore 8.58
    G_M51821_IG02:
           xor      ecx, ecx
           vxorps   xmm0, xmm0
           vmovdqu  xmmword ptr [rsp+38H], xmm0
           vmovdqu  xmmword ptr [rsp+48H], xmm0
           mov      qword ptr [rsp+58H], rcx
           mov      gword ptr [rsp+28H], r8
           mov      dword ptr [rsp+30H], -1
           lea      rcx, [rsp+28H]
           call     System.Runtime.CompilerServices.AsyncMethodBuilderCore:Start(byref)
  20. benaadams commented on Jun 18, 2020

    @benaadams
    MemberAuthor

    Will raise new issue; have an easier repo

  21. benaadams commented on Jun 18, 2020

    @benaadams
    MemberAuthor

    Opened new issue with a simpler repo #38070

  22. ghost locked as resolved and limited conversation to collaborators on Dec 10, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions