Skip to content

Fold GDV checks for inlined delegates #84872

Closed
@EgorBo

Description

@EgorBo

Similiar to what #84661 does but for delegates now, e.g.:

static int Test(int a) => InvokeFunc(x => x + a);

static int InvokeFunc(Func<int, int> func) => func(100);

Codegen for Test(int a) in Tier1 with Dynamic PGO enabled:

G_M38279_IG01:  
       push     rdi
       push     rsi
       sub      rsp, 40
       mov      esi, ecx
G_M38279_IG02: 
       mov      rcx, 0x7FF86A8D7078      ; Prog+<>c__DisplayClass1_0
       call     CORINFO_HELP_NEWSFAST
       mov      rdi, rax
       mov      dword ptr [rdi+08H], esi
       mov      rcx, 0x7FF86A8D72F8      ; System.Func`2[int,int]
       call     CORINFO_HELP_NEWSFAST
       mov      rsi, rax
       lea      rcx, bword ptr [rsi+08H]
       mov      rdx, rdi
       call     CORINFO_HELP_ASSIGN_REF
       mov      rax, 0x7FF86A8A8540      ; code for Prog+<>c__DisplayClass1_0:<Test>b__0(int):int:this
       mov      qword ptr [rsi+18H], rax
       cmp      qword ptr [rsi+18H], rax
       jne      SHORT G_M38279_IG05 ;; GDV check
G_M38279_IG03: 
       mov      rax, gword ptr [rsi+08H]
       mov      eax, dword ptr [rax+08H]
       add      eax, 100 ;;;;;;;;;; <-----  inlined lambda!! (100 + a)
G_M38279_IG04: 
       add      rsp, 40
       pop      rsi
       pop      rdi
       ret      
G_M38279_IG05: ;;;; fallback for devirtualized lambda
       mov      rcx, gword ptr [rsi+08H]
       mov      edx, 100
       call     [rsi+18H]System.Func`2[int,int]:Invoke(int):int:this
       jmp      SHORT G_M38279_IG04

The delegate is correctly inlined here under a guard. Since we use a closure here, the delegate is always allocated at the callsite so we always know its exact type and the exact method target so we can optimize the GDV check and remove the software fallback:

       mov      rax, 0x7FF86A8A8540      ; code for Prog+<>c__DisplayClass1_0:<Test>b__0(int):int:this
       mov      qword ptr [rsi+18H], rax
       cmp      qword ptr [rsi+18H], rax
       jne      SHORT G_M38279_IG05

we write a value to a memory location and then compare that location with the same value. @jakobbotsch said the reason CSE didn't fold it is a missing FieldSeq* that we can add during creating that GDV check (might require a new JIT-EE API to get a field handle of _methodPtr field in Delegate).

Follow up questions

  1. Once we remove the check - can we also remove the unnecessary delegate instance allocation?
  2. For static lambdas Roslyn currently emits a non-jit friendly pattern with a static mutable field, e.g. sharplab.io, so there is no way JIT can track the target - that can be improved on the Roslyn side - Discussion: Permit static lambda assignment to function pointers csharplang#6746 (reply in thread)

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

Relationships

None yet

Development

No branches or pull requests

Issue actions