Skip to content

JIT: poor CQ for list enumeration constructs #9304

Open

Description

@AndyAyersMS

From some discussion over in dotnet/csharplang#1085. Both the for and foreach cases have suboptimal CQ.

In the for case there is a redundant branch in the inner loop.

In the foreach case the enumerator's MoveNext is not inlined. If we force this and MoveNextRare inline then the jit is not able to optimize away the version check overhead in the inner loop.

Without the inlines the foreach is rougly 2.9x slower than the for; with the inlines this drops to 1.3x or so.

    static int SumFor(List<int> l)
    {
        int result = 0;

        for (int i = 0; i < l.Count; i++)
        {
            result += l[i];
        }

        return result;
    }
    
    static int SumForeach(List<int> l)
    {
        int result = 0;

        foreach (int i in l)
        {
            result += i;
        }

        return result;
    }

category:cq
theme:basic-cq
skill-level:expert
cost:medium
impact:large

Activity

  1. AndyAyersMS commented on Nov 21, 2017

    @AndyAyersMS
    MemberAuthor

    For SumFor:

    ; Assembly listing for method P:SumFor(ref):int
    
    G_M43955_IG01:
           4883EC28             sub      rsp, 40
    
    G_M43955_IG02:
           33C0                 xor      eax, eax                   // result
           33D2                 xor      edx, edx                   // i
           448B4118             mov      r8d, dword ptr [rcx+24]    // _size
           4585C0               test     r8d, r8d
           7E1E                 jle      SHORT G_M43955_IG05
    
    G_M43955_IG03:
           413BD0               cmp      edx, r8d
           731E                 jae      SHORT G_M43955_IG06
    
    G_M43955_IG04:
           4C8B4908             mov      r9, gword ptr [rcx+8]      // _items
           413B5108             cmp      edx, dword ptr [r9+8]      // array len
           731A                 jae      SHORT G_M43955_IG07
           4C63D2               movsxd   r10, edx
           4303449110           add      eax, dword ptr [r9+4*r10+16]
           FFC2                 inc      edx
           413BD0               cmp      edx, r8d
           7CE2                 jl       SHORT G_M43955_IG03
    
    G_M43955_IG05:
           4883C428             add      rsp, 40
           C3                   ret      
    
    G_M43955_IG06:
           E85758A35E           call     System.ThrowHelper:ThrowArgumentOutOfRange_IndexException()
           CC                   int3     
    
    G_M43955_IG07:
           E831D8365F           call     CORINFO_HELP_RNGCHKFAIL
           CC                   

    IG03 is a critical block (fork/join). The join limits the jit's ability to reason about what happens in this block.

    When reaching IG03 from IG02: r8d > 0, edx = 0. So edx < r8d. So the branch to IG06 is not taken.

    When reaching IG03 from IG04: edx < r8d. So the branch to IG06 is not taken.

    It might be simpler for the jit to reason about IG03 if it simply cloned it to push the join down to IG04.

    IG03's branch makes IG04 "conditionally executed" in the loop. So the jit won't consider hoisting invariants like "mov r9, gword ptr [rcx+8]".

  2. benaadams commented on Nov 21, 2017

    @benaadams
    Member

    There would be more value in inlining MoveNext; if localList._size could be compared to localList._items.Length to prove the loop would never go out of bounds (and for that check to then be hoisted)

    However I think the range check elimination currently only works evaluating to .Length not to a value tested to be <= .Length e.g. _size dotnet/coreclr#14030 (comment)

  3. AndyAyersMS commented on Nov 21, 2017

    @AndyAyersMS
    MemberAuthor

    For SumForeach:

    ; method P:SumFor(ref):int
    ; MoveNext* with AggressiveInline
    
    G_M41030_IG01:
           4883EC28             sub      rsp, 40
    
    G_M41030_IG02:
           33C0                 xor      eax, eax                  // i
           8B11                 mov      edx, dword ptr [rcx]
           488BD1               mov      rdx, rcx
           8B4A1C               mov      ecx, dword ptr [rdx+28]   // _version
           448BC1               mov      r8d, ecx
           448BC8               mov      r9d, eax
    
    G_M41030_IG03:
           EB03                 jmp      SHORT G_M41030_IG05
    
    G_M41030_IG04:
           4103C2               add      eax, r10d
    
    G_M41030_IG05:
           443BC1               cmp      r8d, ecx
           7523                 jne      SHORT G_M41030_IG06
           443B4A18             cmp      r9d, dword ptr [rdx+24]    // _index
           731D                 jae      SHORT G_M41030_IG06
           4C8B5208             mov      r10, gword ptr [rdx+8]     // _list
           453B4A08             cmp      r9d, dword ptr [r10+8]     
           7335                 jae      SHORT G_M41030_IG11
           4D63D9               movsxd   r11, r9d
           478B549A10           mov      r10d, dword ptr [r10+4*r11+16]
           41FFC1               inc      r9d                         
           41BB01000000         mov      r11d, 1
           EB12                 jmp      SHORT G_M41030_IG08
    
    G_M41030_IG06:
           443BC1               cmp      r8d, ecx
           7517                 jne      SHORT G_M41030_IG10
    
    G_M41030_IG07:
           448B4A18             mov      r9d, dword ptr [rdx+24]
           41FFC1               inc      r9d
           4533D2               xor      r10d, r10d
           4533DB               xor      r11d, r11d
    
    G_M41030_IG08:
           4585DB               test     r11d, r11d
           75BE                 jne      SHORT G_M41030_IG04
    
    G_M41030_IG09:
           4883C428             add      rsp, 40
           C3                   ret      
    
    G_M41030_IG10:
           E87E5FA35E           call     System.ThrowHelper:ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion()
           CC                   int3     
    
    G_M41030_IG11:
           E8A8D7365F           call     CORINFO_HELP_RNGCHKFAIL
           CC                   int3     

    IG08 is a also a critical block (testing the result of MoveNext):

    When reached from IG07 r11d is 0 and so the jump is not taken.
    When reached from IG05, r11d is 1, so the jump is always taken.

    By cloning this block we can streamline both branches.

    The jit already realizes the compare and branch at the top of IG05 and IG06 has known outcome but doesn't act on it for some reason (perhaps because we're in SSA and don't want to mess with control flow?):

    optValnumCSE morphed tree:
    N004 (  5,  5)              [000115] ----G-------              *  JTRUE     void  
    N002 (  1,  1)              [000310] ------------              |  /--*  LCL_VAR   int    V17 cse0          <l:$281, c:$103>
    N003 (  3,  3)              [000114] N---G--N-U--              \--*  NE        int    <l:$40, c:$284>
    N001 (  1,  1)              [000111] ------------                 \--*  LCL_VAR   int    V09 tmp5         u:3 <l:$281, c:$103>
    
    optValnumCSE morphed tree:
    N004 (  5,  5)              [000179] ----G-------              *  JTRUE     void  
    N002 (  1,  1)              [000311] ------------              |  /--*  LCL_VAR   int    V17 cse0          <l:$281, c:$103>
    N003 (  3,  3)              [000178] J---G--N----              \--*  NE        int    <l:$40, c:$289>
    N001 (  1,  1)              [000173] ------------                 \--*  LCL_VAR   int    V09 tmp5         u:3 <l:$281, c:$103>
    

    note the two operands have the same value numbers.

    Fixing all that would leave us with an inner loop something like:

    G_M41030_IG04:
           4103C2               add      eax, r10d
    
    G_M41030_IG05:
           443B4A18             cmp      r9d, dword ptr [rdx+24]    // _index
           731D                 jae      SHORT G_M41030_IG10
           4C8B5208             mov      r10, gword ptr [rdx+8]     // _list
           453B4A08             cmp      r9d, dword ptr [r10+8]     
           7335                 jae      SHORT G_M41030_IG11
           4D63D9               movsxd   r11, r9d
           478B549A10           mov      r10d, dword ptr [r10+4*r11+16]
           41FFC1               inc      r9d                         
           EB12                 jmp      SHORT G_M41030_IG04

    where we then might be able to host some of the loads off of rdx and we'd have code that was close to the for version.

  4. transferred this issue fromdotnet/coreclron Jan 31, 2020
  5. added this to the Future milestone on Jan 31, 2020
  6. jakobbotsch commented on Apr 17, 2024

    @jakobbotsch
    Member

    Codegen today:

    ; Assembly listing for method Program:SumFor(System.Collections.Generic.List`1[int]):int (FullOpts)
    ; Emitting BLENDED_CODE for X64 with AVX - Windows
    ; FullOpts code
    ; optimized code
    ; rsp based frame
    ; fully interruptible
    ; No PGO data
    ; 1 inlinees with PGO data; 1 single block inlinees; 0 inlinees without PGO data
    ; Final local variable assignments
    ;
    ;  V00 arg0         [V00,T04] (  4,  7   )     ref  ->  rcx         class-hnd single-def <System.Collections.Generic.List`1[int]>
    ;  V01 loc0         [V01,T02] (  4, 10   )     int  ->  rax        
    ;* V02 loc1         [V02,T05] (  0,  0   )     int  ->  zero-ref   
    ;  V03 OutArgs      [V03    ] (  1,  1   )  struct (32) [rsp+0x00]  do-not-enreg[XS] addr-exposed "OutgoingArgSpace"
    ;  V04 tmp1         [V04,T01] (  3, 24   )     ref  ->  r10         "arr expr"
    ;  V05 cse0         [V05,T03] (  4, 10   )     int  ->   r8         "CSE #01: aggressive"
    ;  V06 rat0         [V06,T00] (  7, 25   )    long  ->  rdx         "Widened IV V02"
    ;
    ; Lcl frame size = 40
    
    G_M46421_IG01:  ;; offset=0x0000
           sub      rsp, 40
    						;; size=4 bbWeight=1 PerfScore 0.25
    G_M46421_IG02:  ;; offset=0x0004
           xor      eax, eax
           xor      edx, edx
           mov      r8d, dword ptr [rcx+0x10]
           test     r8d, r8d
           jle      SHORT G_M46421_IG04
           align    [15 bytes for IG03]
    						;; size=28 bbWeight=1 PerfScore 4.00
    G_M46421_IG03:  ;; offset=0x0020
           cmp      edx, r8d
           jae      SHORT G_M46421_IG05
           mov      r10, gword ptr [rcx+0x08]
           cmp      edx, dword ptr [r10+0x08]
           jae      SHORT G_M46421_IG06
           add      eax, dword ptr [r10+4*rdx+0x10]
           inc      edx
           cmp      edx, r8d
           jl       SHORT G_M46421_IG03
    						;; size=27 bbWeight=4 PerfScore 47.00
    G_M46421_IG04:  ;; offset=0x003B
           add      rsp, 40
           ret      
    						;; size=5 bbWeight=1 PerfScore 1.25
    G_M46421_IG05:  ;; offset=0x0040
           call     [System.ThrowHelper:ThrowArgumentOutOfRange_IndexMustBeLessException()]
           int3     
    						;; size=7 bbWeight=0 PerfScore 0.00
    G_M46421_IG06:  ;; offset=0x0047
           call     CORINFO_HELP_RNGCHKFAIL
           int3     
    						;; size=6 bbWeight=0 PerfScore 0.00
    
    ; Total bytes of code 77, prolog size 4, PerfScore 52.50, instruction count 22, allocated bytes for code 77 (MethodHash=44f14aaa) for method Program:SumFor(System.Collections.Generic.List`1[int]):int (FullOpts)
    ; ============================================================
    ; Assembly listing for method Program:SumForeach(System.Collections.Generic.List`1[int]):int (FullOpts)
    ; Emitting BLENDED_CODE for X64 with AVX - Windows
    ; FullOpts code
    ; optimized code
    ; rsp based frame
    ; fully interruptible
    ; No PGO data
    ; 2 inlinees with PGO data; 4 single block inlinees; 0 inlinees without PGO data
    ; Final local variable assignments
    ;
    ;  V00 arg0         [V00,T07] (  3,  3   )     ref  ->  rcx         class-hnd single-def <System.Collections.Generic.List`1[int]>
    ;  V01 loc0         [V01,T04] (  4, 12.27)     int  ->  rax        
    ;* V02 loc1         [V02    ] (  0,  0   )  struct (24) zero-ref    ld-addr-op <System.Collections.Generic.List`1+Enumerator[int]>
    ;* V03 loc2         [V03    ] (  0,  0   )     int  ->  zero-ref   
    ;  V04 OutArgs      [V04    ] (  1,  1   )  struct (32) [rsp+0x00]  do-not-enreg[XS] addr-exposed "OutgoingArgSpace"
    ;* V05 tmp1         [V05    ] (  0,  0   )  struct (24) zero-ref    ld-addr-op "NewObj constructor temp" <System.Collections.Generic.List`1+Enumerator[int]>
    ;* V06 tmp2         [V06,T06] (  0,  0   )   ubyte  ->  zero-ref    "Inline return value spill temp"
    ;* V07 tmp3         [V07    ] (  0,  0   )     ref  ->  zero-ref    class-hnd "Inline stloc first use temp" <System.Collections.Generic.List`1[int]>
    ;  V08 tmp4         [V08,T03] (  3, 12.63)     ref  ->  rcx         single-def "field V02._list (fldOffset=0x0)" P-INDEP
    ;  V09 tmp5         [V09,T00] (  6, 23.54)     int  ->  rdx         "field V02._index (fldOffset=0x8)" P-INDEP
    ;* V10 tmp6         [V10,T11] (  0,  0   )     int  ->  zero-ref    single-def "field V02._version (fldOffset=0xc)" P-INDEP
    ;  V11 tmp7         [V11,T05] (  2,  7.27)     int  ->   r8         "field V02._current (fldOffset=0x10)" P-INDEP
    ;  V12 tmp8         [V12,T08] (  3,  3   )     ref  ->  rcx         single-def "field V05._list (fldOffset=0x0)" P-INDEP
    ;* V13 tmp9         [V13,T12] (  0,  0   )     int  ->  zero-ref    single-def "field V05._index (fldOffset=0x8)" P-INDEP
    ;* V14 tmp10        [V14,T09] (  0,  0   )     int  ->  zero-ref    single-def "field V05._version (fldOffset=0xc)" P-INDEP
    ;* V15 tmp11        [V15    ] (  0,  0   )     int  ->  zero-ref    single-def "field V05._current (fldOffset=0x10)" P-INDEP
    ;  V16 tmp12        [V16,T01] (  3, 21.81)     ref  ->   r8         "arr expr"
    ;* V17 cse0         [V17,T10] (  0,  0   )     int  ->  zero-ref    "CSE #01: aggressive"
    ;  V18 cse1         [V18,T02] (  2, 16   )     int  ->   r8         "CSE #02: aggressive"
    ;
    ; Lcl frame size = 40
    
    G_M40154_IG01:  ;; offset=0x0000
           sub      rsp, 40
    						;; size=4 bbWeight=1 PerfScore 0.25
    G_M40154_IG02:  ;; offset=0x0004
           xor      eax, eax
           mov      edx, dword ptr [rcx+0x14]
           xor      edx, edx
           jmp      SHORT G_M40154_IG04
           align    [0 bytes for IG03]
    						;; size=9 bbWeight=1 PerfScore 4.50
    G_M40154_IG03:  ;; offset=0x000D
           add      eax, r8d
    						;; size=3 bbWeight=3.63 PerfScore 0.91
    G_M40154_IG04:  ;; offset=0x0010
           mov      r8d, dword ptr [rcx+0x10]
           cmp      edx, r8d
           jae      SHORT G_M40154_IG06
    						;; size=9 bbWeight=8 PerfScore 26.00
    G_M40154_IG05:  ;; offset=0x0019
           mov      r8, gword ptr [rcx+0x08]
           cmp      edx, dword ptr [r8+0x08]
           jae      SHORT G_M40154_IG07
           mov      r10d, edx
           mov      r8d, dword ptr [r8+4*r10+0x10]
           inc      edx
           jmp      SHORT G_M40154_IG03
    						;; size=22 bbWeight=3.63 PerfScore 38.16
    G_M40154_IG06:  ;; offset=0x002F
           add      rsp, 40
           ret      
    						;; size=5 bbWeight=4 PerfScore 5.00
    G_M40154_IG07:  ;; offset=0x0034
           call     CORINFO_HELP_RNGCHKFAIL
           int3     
    						;; size=6 bbWeight=0 PerfScore 0.00
    
    ; Total bytes of code 58, prolog size 4, PerfScore 74.82, instruction count 21, allocated bytes for code 58 (MethodHash=ae626325) for method Program:SumForeach(System.Collections.Generic.List`1[int]):int (FullOpts)
    ; ============================================================

    Looks much better today, but there still seems to be a redundant compare in SumFor and some quite suboptimal block layout in SumForeach (might be an interesting case to look at for block layout, cc @amanasifkhalid as well).

    We also seemingly don't manage to widen the IV for the foreach version. I opened #101176 for this.

  7. amanasifkhalid commented on May 17, 2024

    @amanasifkhalid
    Contributor

    With DOTNET_JitDoReversePostOrderLayout=1, SumFor is unchanged, and SumForeach is marginally better:

    ; Assembly listing for method Program:SumForeach(System.Collections.Generic.List`1[int]):int (FullOpts)
    ; Emitting BLENDED_CODE for X64 with AVX - Windows
    ; FullOpts code
    ; optimized code
    ; rsp based frame
    ; fully interruptible
    ; No PGO data
    ; 2 inlinees with PGO data; 4 single block inlinees; 0 inlinees without PGO data
    ; Final local variable assignments
    ;
    ;  V00 arg0         [V00,T07] (  3,  3   )     ref  ->  rcx         class-hnd single-def <System.Collections.Generic.List`1[int]>
    ;  V01 loc0         [V01,T04] (  4, 12.27)     int  ->  rax
    ;* V02 loc1         [V02    ] (  0,  0   )  struct (24) zero-ref    ld-addr-op <System.Collections.Generic.List`1+Enumerator[int]>
    ;* V03 loc2         [V03    ] (  0,  0   )     int  ->  zero-ref
    ;  V04 OutArgs      [V04    ] (  1,  1   )  struct (32) [rsp+0x00]  do-not-enreg[XS] addr-exposed "OutgoingArgSpace"
    ;* V05 tmp1         [V05    ] (  0,  0   )  struct (24) zero-ref    ld-addr-op "NewObj constructor temp" <System.Collections.Generic.List`1+Enumerator[int]>
    ;* V06 tmp2         [V06,T06] (  0,  0   )   ubyte  ->  zero-ref    "Inline return value spill temp"
    ;* V07 tmp3         [V07    ] (  0,  0   )     ref  ->  zero-ref    class-hnd "Inline stloc first use temp" <System.Collections.Generic.List`1[int]>
    ;  V08 tmp4         [V08,T03] (  3, 12.63)     ref  ->  rcx         single-def "field V02._list (fldOffset=0x0)" P-INDEP
    ;  V09 tmp5         [V09,T00] (  6, 23.54)     int  ->  rdx         "field V02._index (fldOffset=0x8)" P-INDEP
    ;* V10 tmp6         [V10,T11] (  0,  0   )     int  ->  zero-ref    single-def "field V02._version (fldOffset=0xc)" P-INDEP
    ;  V11 tmp7         [V11,T05] (  2,  7.27)     int  ->   r8         "field V02._current (fldOffset=0x10)" P-INDEP
    ;  V12 tmp8         [V12,T08] (  3,  3   )     ref  ->  rcx         single-def "field V05._list (fldOffset=0x0)" P-INDEP
    ;* V13 tmp9         [V13,T12] (  0,  0   )     int  ->  zero-ref    single-def "field V05._index (fldOffset=0x8)" P-INDEP
    ;* V14 tmp10        [V14,T09] (  0,  0   )     int  ->  zero-ref    single-def "field V05._version (fldOffset=0xc)" P-INDEP
    ;* V15 tmp11        [V15    ] (  0,  0   )     int  ->  zero-ref    single-def "field V05._current (fldOffset=0x10)" P-INDEP
    ;  V16 tmp12        [V16,T01] (  3, 21.81)     ref  ->   r8         "arr expr"
    ;* V17 cse0         [V17,T10] (  0,  0   )     int  ->  zero-ref    "CSE #01: aggressive"
    ;  V18 cse1         [V18,T02] (  2, 16   )     int  ->   r8         "CSE #02: aggressive"
    ;
    ; Lcl frame size = 40
    
    G_M40154_IG01:  ;; offset=0x0000
           sub      rsp, 40
                                                    ;; size=4 bbWeight=1 PerfScore 0.25
    G_M40154_IG02:  ;; offset=0x0004
           xor      eax, eax
           mov      edx, dword ptr [rcx+0x14]
           xor      edx, edx
           align    [0 bytes for IG03]
                                                    ;; size=7 bbWeight=1 PerfScore 2.50
    G_M40154_IG03:  ;; offset=0x000B
           mov      r8d, dword ptr [rcx+0x10]
           cmp      edx, r8d
           jae      SHORT G_M40154_IG05
                                                    ;; size=9 bbWeight=8 PerfScore 26.00
    G_M40154_IG04:  ;; offset=0x0014
           mov      r8, gword ptr [rcx+0x08]
           cmp      edx, dword ptr [r8+0x08]
           jae      SHORT G_M40154_IG06
           mov      r10d, edx
           mov      r8d, dword ptr [r8+4*r10+0x10]
           inc      edx
           add      eax, r8d
           jmp      SHORT G_M40154_IG03
                                                    ;; size=25 bbWeight=3.63 PerfScore 39.07
    G_M40154_IG05:  ;; offset=0x002D
           add      rsp, 40
           ret
                                                    ;; size=5 bbWeight=4 PerfScore 5.00
    G_M40154_IG06:  ;; offset=0x0032
           call     CORINFO_HELP_RNGCHKFAIL
           int3
                                                    ;; size=6 bbWeight=0 PerfScore 0.00
    
    ; Total bytes of code 56, prolog size 4, PerfScore 72.82, instruction count 20, allocated bytes for code 56 (MethodHash=ae626325) for method Program:SumForeach(System.Collections.Generic.List`1[int]):int (FullOpts)
    ; ============================================================
    

    Looking at the JIT dump, this looks like another case of the loop shape issue mentioned in #102343:

    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BBnum BBid ref try hnd preds           weight   IBC [IL range]   [jump]                            [EH region]        [flags]
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BB01 [0000]  1                             1        [000..009)-> BB02(1)                 (always)                     i nullcheck
    BB02 [0001]  1       BB01                  1        [009..00B)-> BB04(1)                 (always)                     i keep
    BB04 [0003]  2       BB02,BB03             8    800 [017..018)-> BB07(0.0914),BB06(0.909)  ( cond )                     i IBC bwd bwd-src
    BB06 [0014]  1       BB04                  3.63 363 [017..018)-> BB03(1)                 (always)                     i IBC idxlen bwd
    BB03 [0002]  1       BB06                  3.63 363 [00B..017)-> BB04(1)                 (always)                     i IBC loophead bwd bwd-target
    BB07 [0015]  1       BB04                  4    400 [017..032)                           (return)                     i IBC bwd
    BB11 [0022]  0                             0        [???..???)                           (throw )                     i rare keep internal
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    
  8. amanasifkhalid commented on May 21, 2024

    @amanasifkhalid
    Contributor

    The loop rotation issue I noted above is fixed by #102343. Here's the block layout of SumForeach using the old layout implementation:

    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BBnum BBid ref try hnd preds           weight   IBC [IL range]   [jump]                            [EH region]        [flags]
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BB01 [0000]  1                             1        [000..009)-> BB02(1)                 (always)                     i nullcheck
    BB02 [0001]  1       BB01                  1        [009..00B)-> BB04(1)                 (always)                     i keep
    BB03 [0002]  1       BB06                  3.63 363 [00B..017)-> BB04(1)                 (always)                     i IBC loophead bwd bwd-target
    BB04 [0003]  2       BB02,BB03             8    800 [017..018)-> BB07(0.0914),BB06(0.909)  ( cond )                     i IBC bwd bwd-src
    BB06 [0014]  1       BB04                  3.63 363 [017..018)-> BB03(1)                 (always)                     i IBC idxlen bwd
    BB07 [0015]  1       BB04                  4    400 [017..032)                           (return)                     i IBC bwd
    BB11 [0022]  0                             0        [???..???)                           (throw )                     i rare keep internal
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    

    With the new implementation, here's the initial RPO layout from above:

    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BBnum BBid ref try hnd preds           weight   IBC [IL range]   [jump]                            [EH region]        [flags]
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BB01 [0000]  1                             1        [000..009)-> BB02(1)                 (always)                     i nullcheck
    BB02 [0001]  1       BB01                  1        [009..00B)-> BB04(1)                 (always)                     i keep
    BB04 [0003]  2       BB02,BB03             8    800 [017..018)-> BB07(0.0914),BB06(0.909)  ( cond )                     i IBC bwd bwd-src
    BB06 [0014]  1       BB04                  3.63 363 [017..018)-> BB03(1)                 (always)                     i IBC idxlen bwd
    BB03 [0002]  1       BB06                  3.63 363 [00B..017)-> BB04(1)                 (always)                     i IBC loophead bwd bwd-target
    BB07 [0015]  1       BB04                  4    400 [017..032)                           (return)                     i IBC bwd
    BB11 [0022]  0                             0        [???..???)                           (throw )                     i rare keep internal
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    

    Then after we fix up the backward jumps, we end up with this layout:

    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BBnum BBid ref try hnd preds           weight   IBC [IL range]   [jump]                            [EH region]        [flags]
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BB01 [0000]  1                             1        [000..009)-> BB02(1)                 (always)                     i nullcheck
    BB02 [0001]  1       BB01                  1        [009..00B)-> BB04(1)                 (always)                     i keep
    BB03 [0002]  1       BB06                  3.63 363 [00B..017)-> BB04(1)                 (always)                     i IBC loophead bwd bwd-target
    BB04 [0003]  2       BB02,BB03             8    800 [017..018)-> BB07(0.0914),BB06(0.909)  ( cond )                     i IBC bwd bwd-src
    BB06 [0014]  1       BB04                  3.63 363 [017..018)-> BB03(1)                 (always)                     i IBC idxlen bwd
    BB07 [0015]  1       BB04                  4    400 [017..032)                           (return)                     i IBC bwd
    BB11 [0022]  0                             0        [???..???)                           (throw )                     i rare keep internal
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    

    So we're back to square one. We can't use fgOptimizeBranchToEmptyUnconditional to get rid of BB06 because it isn't empty. However, after setting up the RPO-based layout, there is an opportunity to compact BB06 and BB03, and then move the loop head to the top of the loop as usual. This would allow BB04 to fall through upon loop exit. That might seem like an odd transformation to make during layout instead of in fgUpdateFlowGraph, but it does unlock some branch removal opportunities, and we have TP to spare. If I add a pass to compact blocks after getting the RPO-based layout, here's how the new final layout looks:

    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BBnum BBid ref try hnd preds           weight   IBC [IL range]   [jump]                            [EH region]        [flags]
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BB01 [0000]  1                             1        [000..009)-> BB02(1)                 (always)                     i nullcheck
    BB02 [0001]  1       BB01                  1        [009..00B)-> BB04(1)                 (always)                     i keep
    BB06 [0014]  1       BB04                  3.63 363 [00B..018)-> BB04(1)                 (always)                     i IBC idxlen bwd
    BB04 [0003]  2       BB02,BB06             8    800 [017..018)-> BB07(0.0914),BB06(0.909)  ( cond )                     i IBC bwd bwd-src
    BB07 [0015]  1       BB04                  4    400 [017..032)                           (return)                     i IBC bwd
    BB11 [0022]  0                             0        [???..???)                           (throw )                     i rare keep internal
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    

    And final codegen:

    ; Assembly listing for method Program:SumForeach(System.Collections.Generic.List`1[int]):int (FullOpts)
    ; Emitting BLENDED_CODE for X64 with AVX - Windows
    ; FullOpts code
    ; optimized code
    ; rsp based frame
    ; fully interruptible
    ; No PGO data
    ; 2 inlinees with PGO data; 4 single block inlinees; 0 inlinees without PGO data
    ; Final local variable assignments
    ;
    ;  V00 arg0         [V00,T07] (  3,  3   )     ref  ->  rcx         class-hnd single-def <System.Collections.Generic.List`1[int]>
    ;  V01 loc0         [V01,T04] (  4, 12.27)     int  ->  rax
    ;* V02 loc1         [V02    ] (  0,  0   )  struct (24) zero-ref    ld-addr-op <System.Collections.Generic.List`1+Enumerator[int]>
    ;* V03 loc2         [V03    ] (  0,  0   )     int  ->  zero-ref
    ;  V04 OutArgs      [V04    ] (  1,  1   )  struct (32) [rsp+0x00]  do-not-enreg[XS] addr-exposed "OutgoingArgSpace"
    ;* V05 tmp1         [V05    ] (  0,  0   )  struct (24) zero-ref    ld-addr-op "NewObj constructor temp" <System.Collections.Generic.List`1+Enumerator[int]>
    ;* V06 tmp2         [V06,T06] (  0,  0   )   ubyte  ->  zero-ref    "Inline return value spill temp"
    ;* V07 tmp3         [V07    ] (  0,  0   )     ref  ->  zero-ref    class-hnd "Inline stloc first use temp" <System.Collections.Generic.List`1[int]>
    ;  V08 tmp4         [V08,T03] (  3, 12.63)     ref  ->  rcx         single-def "field V02._list (fldOffset=0x0)" P-INDEP
    ;  V09 tmp5         [V09,T00] (  6, 23.54)     int  ->  rdx         "field V02._index (fldOffset=0x8)" P-INDEP
    ;* V10 tmp6         [V10,T11] (  0,  0   )     int  ->  zero-ref    single-def "field V02._version (fldOffset=0xc)" P-INDEP
    ;  V11 tmp7         [V11,T05] (  2,  7.27)     int  ->   r8         "field V02._current (fldOffset=0x10)" P-INDEP
    ;  V12 tmp8         [V12,T08] (  3,  3   )     ref  ->  rcx         single-def "field V05._list (fldOffset=0x0)" P-INDEP
    ;* V13 tmp9         [V13,T12] (  0,  0   )     int  ->  zero-ref    single-def "field V05._index (fldOffset=0x8)" P-INDEP
    ;* V14 tmp10        [V14,T09] (  0,  0   )     int  ->  zero-ref    single-def "field V05._version (fldOffset=0xc)" P-INDEP
    ;* V15 tmp11        [V15    ] (  0,  0   )     int  ->  zero-ref    single-def "field V05._current (fldOffset=0x10)" P-INDEP
    ;  V16 tmp12        [V16,T01] (  3, 21.81)     ref  ->   r8         "arr expr"
    ;* V17 cse0         [V17,T10] (  0,  0   )     int  ->  zero-ref    "CSE #01: aggressive"
    ;  V18 cse1         [V18,T02] (  2, 16   )     int  ->   r8         "CSE #02: aggressive"
    ;
    ; Lcl frame size = 40
    
    G_M40154_IG01:  ;; offset=0x0000
           sub      rsp, 40
                                                    ;; size=4 bbWeight=1 PerfScore 0.25
    G_M40154_IG02:  ;; offset=0x0004
           xor      eax, eax
           mov      edx, dword ptr [rcx+0x14]
           xor      edx, edx
           jmp      SHORT G_M40154_IG04
           align    [3 bytes for IG03]
                                                    ;; size=12 bbWeight=1 PerfScore 4.50
    G_M40154_IG03:  ;; offset=0x0010
           mov      r8, gword ptr [rcx+0x08]
           cmp      edx, dword ptr [r8+0x08]
           jae      SHORT G_M40154_IG06
           mov      r10d, edx
           mov      r8d, dword ptr [r8+4*r10+0x10]
           inc      edx
           add      eax, r8d
                                                    ;; size=23 bbWeight=3.63 PerfScore 31.80
    G_M40154_IG04:  ;; offset=0x0027
           mov      r8d, dword ptr [rcx+0x10]
           cmp      edx, r8d
           jb       SHORT G_M40154_IG03
                                                    ;; size=9 bbWeight=8 PerfScore 26.00
    G_M40154_IG05:  ;; offset=0x0030
           add      rsp, 40
           ret
                                                    ;; size=5 bbWeight=4 PerfScore 5.00
    G_M40154_IG06:  ;; offset=0x0035
           call     CORINFO_HELP_RNGCHKFAIL
           int3
                                                    ;; size=6 bbWeight=0 PerfScore 0.00
    
    ; Total bytes of code 59, prolog size 4, PerfScore 67.55, instruction count 20, allocated bytes for code 59 (MethodHash=ae626325) for method Program:SumForeach(System.Collections.Generic.List`1[int]):int (FullOpts)
    ; ============================================================
    

    I think this warrants a follow-up to #102461.

    While unrelated to this issue, we don't handle the BBJ_COND to empty BBJ_ALWAYS case with fgOptimizeBranchToEmptyUnconditional just yet, due to the old block layout's dependence on implicit fallthrough from conditional blocks into their false targets. Once #102343 is merged, I think we should pursue that in fgUpdateFlowGraph.

  9. AndyAyersMS commented on Jul 3, 2025

    @AndyAyersMS
    MemberAuthor

    @amanasifkhalid perhaps this one is worth revisiting?

  10. amanasifkhalid commented on Jul 3, 2025

    @amanasifkhalid
    Contributor

    Sure. The loop bodies are correctly rotated in the OSR variants.

    SumFor:

    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BBnum BBid ref try hnd preds           weight   IBC [IL range]   [jump]                            [EH region]        [flags]
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BB05 [0004]  1                             1      1 [???..???)-> BB04(0.00595),BB10(0.994)   ( cond )                   i LIR IBC internal
    BB10 [0010]  1       BB05                  0.99   1 [???..???)-> BB02(1)                 (always)                   i LIR IBC internal
    BB02 [0001]  2       BB03,BB10           167.07 167 [006..007)-> BB09(1),BB08(0)         ( cond )                   i LIR IBC bwd bwd-target
    BB09 [0007]  1       BB02                167.07 167 [006..014)-> BB03(1)                 (always)                   i LIR IBC idxlen bwd
    BB03 [0002]  1       BB09                167.07 167 [014..01D)-> BB02(0.994),BB04(0.00595)   ( cond )                   i LIR IBC bwd bwd-src osr-entry
    BB04 [0003]  2       BB03,BB05             1.00   1 [01D..01F)                           (return)                   i LIR IBC
    BB08 [0006]  1       BB02                  0      0 [006..007)                           (throw )                   i LIR IBC rare hascall gcsafe bwd
    BB12 [0012]  0                             0      0 [???..???)                           (throw )                   i LIR IBC rare keep internal
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    

    SumForeach:

    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BBnum BBid ref try hnd preds           weight   IBC [IL range]   [jump]                            [EH region]        [flags]
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BB08 [0007]  1                             1      1 [???..???)-> BB04(1),BB19(0)         ( cond )                   i LIR IBC internal
    BB15 [0016]  1       BB04                  4.33   4 [00B..018)-> BB04(1)                 (always)                   i LIR IBC idxlen bwd
    BB04 [0003]  2       BB08,BB15             5.33   5 [017..018)-> BB16(0.188),BB15(0.812) ( cond )                   i LIR IBC bwd bwd-src osr-entry
    BB16 [0017]  1       BB04                  1.00   1 [017..032)                           (return)                   i LIR IBC bwd
    BB19 [0020]  1       BB08                  0      0 [017..018)                           (throw )                   i LIR IBC rare hascall gcsafe bwd
    BB23 [0026]  0                             0      0 [???..???)                           (throw )                   i LIR IBC rare keep internal
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    

    Notice that the loop weights were flattened at some point before layout. Looking at the JIT dump, we lose some edge likelihood fidelity during tail duplication. I'll take a closer look at this.

    With tiering disabled, our block weight heuristics are good enough to keep the loops rotated correctly.

    SumFor:

    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BBnum BBid ref try hnd preds           weight   IBC [IL range]   [jump]                            [EH region]        [flags]
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BB01 [0000]  1                             1        [000..006)-> BB04(0.5),BB09(0.5)     ( cond )                   i LIR
    BB09 [0009]  2       BB01,BB08             4    400 [006..007)-> BB08(1),BB07(0)         ( cond )                   i LIR IBC bwd
    BB08 [0006]  1       BB09                  4    400 [006..01D)-> BB09(0.5),BB04(0.5)     ( cond )                   i LIR IBC idxlen bwd
    BB04 [0003]  2       BB01,BB08             1        [01D..01F)                           (return)                   i LIR
    BB07 [0005]  1       BB09                  0      0 [006..007)                           (throw )                   i LIR IBC rare hascall gcsafe bwd
    BB11 [0011]  0                             0        [???..???)                           (throw )                   i LIR rare keep internal
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    

    SumForeach:

    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BBnum BBid ref try hnd preds           weight   IBC [IL range]   [jump]                            [EH region]        [flags]
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BB01 [0000]  1                             0.50     [000..00B)-> BB12(1)                 (always)                   i LIR nullcheck
    BB13 [0014]  1       BB12                  4    400 [00B..018)-> BB12(1)                 (always)                   i LIR IBC idxlen bwd
    BB12 [0013]  2       BB01,BB13             4    400 [017..018)-> BB14(0.188),BB13(0.812) ( cond )                   i LIR IBC bwd
    BB14 [0015]  1       BB12                  1    100 [017..032)                           (return)                   i LIR IBC bwd
    BB21 [0024]  0                             0        [???..???)                           (throw )                   i LIR rare keep internal
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    

    In both scenarios, the block layout looks ideal. I still see the redundant compares in SumFor:

    G_M46421_IG01:        ; func=00, offs=0x000000, size=0x0014, bbWeight=1, PerfScore 6.25, gcrefRegs=0000 {}, byrefRegs=0000 {}, byref, nogc <-- Prolog IG
    
    IN0014: 000000 sub      rsp, 40
    IN0015: 000004 mov      rax, gword ptr [rsp+0xB0]
    IN0016: 00000C mov      ecx, dword ptr [rsp+0x64]
    IN0017: 000010 mov      edx, dword ptr [rsp+0x60]
    
    G_M46421_IG02:        ; offs=0x000014, size=0x0009, bbWeight=1, PerfScore 3.25, gcrefRegs=0001 {rax}, byrefRegs=0000 {}, BB05 [0004], byref, isz
    
    IN0001: 000014 mov      r8d, dword ptr [rax+0x10]
    IN0002: 000018 cmp      edx, r8d
    IN0003: 00001B jge      SHORT G_M46421_IG05
    
    G_M46421_IG03:        ; offs=0x00001D, size=0x0003, bbWeight=0.99, PerfScore 0.50, gcrefRegs=0001 {rax}, byrefRegs=0000 {}, BB10 [0010], byref, isz, align
    
    IN0004: 00001D mov      edx, edx
    IN0005: 00001F align    [1 bytes for IG04]
    
    G_M46421_IG04:        ; offs=0x000020, size=0x001B, bbWeight=167.07, PerfScore 1963.04, gcrefRegs=0001 {rax}, byrefRegs=0000 {}, loop=IG04, BB02 [0001], BB09 [0007], BB03 [0002], byref, isz
    
    IN0006: 000020 cmp      edx, r8d
    IN0007: 000023 jae      SHORT G_M46421_IG07
    IN0008: 000025 mov      r10, gword ptr [rax+0x08]
    IN0009: 000029 cmp      edx, dword ptr [r10+0x08]
    IN000a: 00002D jae      SHORT G_M46421_IG08
    IN000b: 00002F add      ecx, dword ptr [r10+4*rdx+0x10]
    IN000c: 000034 inc      edx
    IN000d: 000036 cmp      edx, r8d
    IN000e: 000039 jl       SHORT G_M46421_IG04
    
    G_M46421_IG05:        ; offs=0x00003B, size=0x0002, bbWeight=1.00, PerfScore 0.25, gcrefRegs=0000 {}, byrefRegs=0000 {}, BB04 [0003], byref
    
    IN000f: 00003B mov      eax, ecx
    
    G_M46421_IG06:        ; offs=0x00003D, size=0x0009, bbWeight=1.00, PerfScore 1.75, epilog, nogc, extend
    
    IN0018: 00003D add      rsp, 160
    IN0019: 000044 pop      rbp
    IN001a: 000045 ret      
    
    G_M46421_IG07:        ; offs=0x000046, size=0x0007, bbWeight=0, PerfScore 0.00, gcVars=0000000000000000 {}, gcrefRegs=0000 {}, byrefRegs=0000 {}, BB08 [0006], gcvars, byref
    
    IN0010: 000046 call     [System.ThrowHelper:ThrowArgumentOutOfRange_IndexMustBeLessException()]
    IN0011: 00004C int3     
    
    G_M46421_IG08:        ; offs=0x00004D, size=0x0006, bbWeight=0, PerfScore 0.00, gcrefRegs=0000 {}, byrefRegs=0000 {}, BB12 [0012], byref
    
    IN0012: 00004D call     CORINFO_HELP_RNGCHKFAIL
    IN0013: 000052 int3     
  11. amanasifkhalid commented on Jul 3, 2025

    @amanasifkhalid
    Contributor

    Looking at the JIT dump, we lose some edge likelihood fidelity during tail duplication. I'll take a closer look at this.

    Before flow opts, we have this:

    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BBnum BBid ref try hnd preds           weight   IBC [IL range]   [jump]                            [EH region]        [flags]
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BB08 [0007]  1                             1      1 [???..???)-> BB04(1)                 (always)                   i IBC internal
    BB01 [0000]  0                             1      1 [000..009)-> BB04(1)                 (always)                   i IBC nullcheck
    BB03 [0002]  1       BB12                167.07 167 [00B..017)-> BB04(1)                 (always)                   i IBC bwd bwd-target
    BB04 [0003]  3       BB01,BB03,BB08      168.07 168 [017..018)-> BB16(0),BB14(1)         ( cond )                   i IBC bwd bwd-src osr-entry
    BB14 [0015]  1       BB04                168.07 168 [017..018)-> BB16(0.188),BB15(0.812) ( cond )                   i IBC bwd
    BB15 [0016]  1       BB14                136.55 137 [017..018)-> BB12(1)                 (always)                   i IBC idxlen bwd
    BB16 [0017]  2       BB04,BB14            31.52  32 [017..018)-> BB20(1),BB19(0)         ( cond )                   i IBC bwd
    BB19 [0020]  1       BB16                  0      0 [017..018)                           (throw )                   i IBC rare hascall gcsafe bwd
    BB20 [0021]  1       BB16                 31.52  32 [017..018)-> BB12(1)                 (always)                   i IBC bwd
    BB12 [0018]  2       BB15,BB20           168.07 168 [017..020)-> BB03(0.994),BB05(0.00595)   ( cond )                   i IBC bwd bwd-src
    BB05 [0004]  1       BB12                  1.00   1 [020..032)                           (return)                   i IBC
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    

    First, we clone the condition in BB12 into BB15, and then compact BB12 into BB20. Then, we fold the conditions in BB12 and BB20, such that BB12 jumps to BB03 (which is also compacted with BB04) and BB20 always exits the loop. This folding gets rid of the backedge likelihoods, leaving BB14 to control the loop's iteration. So we have the following:

    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BBnum BBid ref try hnd preds           weight   IBC [IL range]   [jump]                            [EH region]        [flags]
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    BB08 [0007]  1                             1      1 [???..???)-> BB04(1)                 (always)                   i IBC internal
    BB04 [0003]  2       BB08,BB15           167.07 167 [017..018)-> BB16(0),BB14(1)         ( cond )                   i IBC bwd bwd-src osr-entry
    BB14 [0015]  1       BB04                168.07 168 [017..018)-> BB16(0.188),BB15(0.812) ( cond )                   i IBC bwd
    BB15 [0016]  1       BB14                136.55 137 [00B..018)-> BB04(1)                 (always)                   i IBC idxlen bwd
    BB16 [0017]  2       BB04,BB14            31.52  32 [017..018)-> BB20(1),BB19(0)         ( cond )                   i IBC bwd
    BB19 [0020]  1       BB16                  0      0 [017..018)                           (throw )                   i IBC rare hascall gcsafe bwd
    BB20 [0021]  1       BB16                 31.52  32 [017..032)                           (return)                   i IBC bwd
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
    

    These transformations have reduced the loop iteration likelihood from >99% to about 81%, hence why profile repair flattens the loop weights. I tried pattern-matching this case during flow opts to adjust the likelihoods as needed, but it's not easy to do this precisely without building dominators.

    Since this transformation happens before loop opts, I think we can consider PGO-derived loop weights to be pretty trustworthy. I'm tempted to introduce a synthesis phase before loop opts to set loop continuation likelihoods to maintain the loop's cyclic probability. For the above case, we'd see that BB14 -> BB15 will drain the loop of its weight when profile repair runs, and increase the edge's likelihood accordingly. @AndyAyersMS how inadvisable does this sound?

  12. AndyAyersMS commented on Jul 3, 2025

    @AndyAyersMS
    MemberAuthor

    I want to make sure I understand where things go wrong.

    I think the root problem here is that when we duplicate BB12 into BB15, we assume that the edge likelihood ratios remain the same, but in this case the ratios are correlated to the predecessor, and so when we simplify the original BB12 (now BB2) to always return, we arguably should go adjust the likelihoods of BB15 to be more likely to stay in the loop.

    One route to a fix for this is to do limited path profiling for "critical blocks" like BB12 that have multiple predecessors and multiple successors, so we try and track the per-predecessor likelihoods. In particular the 2,2 case will arise a lot because of the lowering of for loops and similar. The downside of this is more complex profiling, and extra burden representing and maintaining these different views of the edge likelihoods.

    Another is to try and remember that when we duplicate a block like these the two sets of edges are linked, and so if we simplify one set we need to adjust the other. I suspect it's unworkable in general, though perhaps if the duplication / simplification happen close together in time it might work out.

    Your idea of preserving loop iteration counts also seems reasonable (at least in this case) though if there are many back edges / exit blocks it might be tricky to figure out how to fix things up.

    Also note loop iteration counts themselves are not always invariants. For loop inversion it is tricky. A loop that zero-trips 99 times and iterates 100x the other time will show a roughly 50/50 likelihood in the critical block, but once inversion splits that block the loop avoidance block should have a 99/1 bias to avoid entering the loop and the in-loop block a 1/99 bias to remain in the loop.

  13. amanasifkhalid commented on Jul 3, 2025

    @amanasifkhalid
    Contributor

    Another is to try and remember that when we duplicate a block like these the two sets of edges are linked, and so if we simplify one set we need to adjust the other. I suspect it's unworkable in general, though perhaps if the duplication / simplification happen close together in time it might work out.

    When we tail-duplicate from some block Y into block X, and then successfully fold X, we try to fold Y as well, so tracking both sets of conditions can be done pretty locally. For the above example, are you suggesting that after we fold both condition copies to jump to different targets, we backtrack to BB14 and adjust its successor likelihoods? My concern with this approach is we don't have the tools during flow opts to easily determine if the paths out of BB14 are independent.

  14. AndyAyersMS commented on Jul 3, 2025

    @AndyAyersMS
    MemberAuthor

    Ah, ok. I think what you're saying is that there is an intermediate state something like this, after the initial duplication and merging.

    Image

    where the red and blue edges are the linked pairs (copies of 12's edges). Now if we are able to simplify the condition at 20+12 so that it always exits to 5, we have a contradiction, since removing the red edge out of 20+12 pushes too much profile into 5.

    And the only viable repair is to reduce the profile flowing into 20+12, which means non-locally finding some dominator of 20+12 and tweaking the likelihoods there, since no matter how we adjust the paired edge on 15 there is no way to fix things to make them consistent.

    What this is telling us was that the incoming profile must already have represented other executions of this code (say all this came from inlining) and once we specialize it to our context it does not hold together.

  15. amanasifkhalid commented on Jul 3, 2025

    @amanasifkhalid
    Contributor

    What this is telling us was that the incoming profile must already have represented other executions of this code (say all this came from inlining) and once we specialize it to our context it does not hold together.

    Right, in this case BB14 came from System.Collections.Generic.List`1+Enumerator[int]:MoveNext() being inlined. This particular run came from calling SumForeach with a List of size 10000; because of OSR, the loop looks like it never exits, so we cap and blend the loop edge likelihoods, thus giving us the initial cyclic probability of ~168. It's a shame that this eventually gets diluted down to ~5, though I suppose there isn't an easy way to cut through the noise of inlinee profiles.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIenhancementProduct code improvement that does NOT require public API changes/additionsoptimizationtenet-performancePerformance related issue

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions