Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

JIT: improve return types in cases with spill temps #15766

Merged
merged 1 commit into from
Jan 8, 2018

Conversation

AndyAyersMS
Copy link
Member

If the jit sees that an inlinee has multiple return sites or has gc ref locals
it will choose to return the inline result via a temp. The jit was not assigning
a type to that temp and so losing track of some type information.

So, for inlinees returning ref types, initially type the return spill temp with
the declared return type of the method.

When importing we may discover that particular return sites will return more
specific types. If all discovered return sites agree, we can update the return
type for the spill temp to match the consensus improved type.

This can lead to removal of some type checks and also to devirtualization.

Addresses issues discussed in #9908 and #15743.

If the jit sees that an inlinee has multiple return sites or has gc ref locals
it will choose to return the inline result via a temp. The jit was not assigning
a type to that temp and so losing track of some type information.

So, for inlinees returning ref types, initially type the return spill temp with
the declared return type of the method.

When importing we may discover that particular return sites will return more
specific types. If all discovered return sites agree, we can update the return
type for the spill temp to match the consensus improved type.

This can lead to removal of some type checks and also to devirtualization.

Addresses issues discussed in #9908 and dotnet#15743.
@AndyAyersMS
Copy link
Member Author

@briansull PTAL
cc @dotnet/jit-contrib @benaadams

Jit-diffs with Ben's prospectve changes to ArrayPool

Total bytes of diff: -160 (0.00% of base)
    diff is an improvement.
Total byte diff includes 0 bytes from reconciling methods
        Base had    0 unique methods,        0 unique bytes
        Diff had    0 unique methods,        0 unique bytes
Top file improvements by size (bytes):
        -136 : System.Private.CoreLib.dasm (0.00% of base)
         -24 : Microsoft.CodeAnalysis.VisualBasic.dasm (0.00% of base)
2 total files with size differences (2 improved, 0 regressed), 128 unchanged.
Top method improvements by size (bytes):
         -15 : System.Private.CoreLib.dasm - Microsoft.Win32.RegistryKey:GetValueNames():ref:this
         -12 : Microsoft.CodeAnalysis.VisualBasic.dasm - Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax.Parser:ResyncAt(struct,int,ref):this
         -12 : Microsoft.CodeAnalysis.VisualBasic.dasm - Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax.Parser:ConsumeUnexpectedTokens(ref):ref:this
         -12 : System.Private.CoreLib.dasm - System.IO.BinaryWriter:Write(struct):this (3 methods)
          -9 : System.Private.CoreLib.dasm - Microsoft.Win32.RegistryKey:GetSubKeyNames():ref:this
22 total methods with size differences (22 improved, 0 regressed), 143423 unchanged.

All the corelib changes are devirtualizations of Rent and Return.

@AndyAyersMS
Copy link
Member Author

Think there is maybe some potential for skew between updating arm tests and updating the jit...? Not sure how else to explain this arm failure and this armlb failure

@dotnet-bot test Windows_NT arm Cross Checked Innerloop Build and Test
@dotnet-bot test Windows_NT armlb Cross Checked Innerloop Build and Test

@benaadams
Copy link
Member

benaadams commented Jan 6, 2018

Seems happy now; could it have been the myget outage? https://github.com/dotnet/coreclr/issues/15769

Copy link

@briansull briansull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@AndyAyersMS AndyAyersMS merged commit a764455 into dotnet:master Jan 8, 2018
@AndyAyersMS AndyAyersMS deleted the DevirtMultipleReturnCase branch January 8, 2018 16:44
@benaadams
Copy link
Member

With the if it doesn't do the sharpening yet?

impDevirtualizeCall: Trying to devirtualize virtual call:
    class for 'this' is ArrayPool`1 (attrib 20000400)
    base method is ArrayPool`1::Rent
    devirt to ArrayPool`1::Rent -- speculative
               [000013] --C-G-------              *  CALLV ind ref    ArrayPool`1.Rent
               [000115] ------------ this in rcx  +--*  LCL_VAR   ref    V07 tmp1         
               [000012] ------------ arg1         \--*  LCL_VAR   int    V02 arg2         
    Class not final or exact, method not final, no devirtualization
Expanding INLINE_CANDIDATE in statement [000081] in BB08:
               [000081] ------------              *  STMT      void  (IL 0x040...  ???)
               [000080] I-C-G-------              \--*  CALL      ref    ArrayPool`1.get_Shared (exactContextHnd=0x0000019B1A20C259)
INLINER: inlineInfo.tokenLookupContextHandle for ArrayPool`1:get_Shared():ref set to 0x0000019B1A20C259:

Invoking compiler for the inlinee method ArrayPool`1:get_Shared():ref :
IL to import:
IL_0000  d0 a6 00 00 1b    ldtoken      0x1B0000A6
IL_0005  28 2a 09 00 06    call         0x600092A
IL_000a  d0 a5 00 00 02    ldtoken      0x20000A5
IL_000f  28 2a 09 00 06    call         0x600092A
IL_0014  28 31 09 00 06    call         0x6000931
IL_0019  2d 1b             brtrue.s     27 (IL_0036)
IL_001b  d0 a6 00 00 1b    ldtoken      0x1B0000A6
IL_0020  28 2a 09 00 06    call         0x600092A
IL_0025  d0 a6 00 00 02    ldtoken      0x20000A6
IL_002a  28 2a 09 00 06    call         0x600092A
IL_002f  28 31 09 00 06    call         0x6000931
IL_0034  2c 06             brfalse.s    6 (IL_003c)
IL_0036  7e 5c 04 00 0a    ldsfld       0xA00045C
IL_003b  2a                ret         
IL_003c  7e 5d 04 00 0a    ldsfld       0xA00045D
IL_0041  2a                ret         

INLINER impTokenLookupContextHandle for ArrayPool`1:get_Shared():ref is 0x0000019B1A20C259.
*************** In fgFindBasicBlocks() for ArrayPool`1:get_Shared():ref
Jump targets:
  IL_0036
  IL_003c
New Basic Block BB19 [0016] created.
BB19 [000..01B)
New Basic Block BB20 [0017] created.
BB20 [01B..036)
New Basic Block BB21 [0018] created.
BB21 [036..03C)
New Basic Block BB22 [0019] created.
BB22 [03C..042)

lvaGrabTemp returning 8 (V08 tmp2) (a long lifetime temp) called for Inline return value spill temp.

lvaSetClass: setting class for V08 to (0000019B1A20C258) ArrayPool`1 
Basic block list for 'ArrayPool`1:get_Shared():ref'

--------------------------------------------------------------------------------------------------------------------------------------
BBnum BBid ref try hnd                 weight    [IL range]     [jump]      [EH region]         [flags]
--------------------------------------------------------------------------------------------------------------------------------------
BB19 [0016]  1                             1     [000..01B)-> BB21 ( cond )                     
BB20 [0017]  1                             1     [01B..036)-> BB22 ( cond )                     
BB21 [0018]  2                             1     [036..03C)        (return)                     
BB22 [0019]  1                             1     [03C..042)        (return)                     
--------------------------------------------------------------------------------------------------------------------------------------
*************** In impImport() for ArrayPool`1:get_Shared():ref

impImportBlockPending for BB19

Importing BB19 (PC=000) of 'ArrayPool`1:get_Shared():ref'
    [ 0]   0 (0x000) ldtoken
    [ 1]   5 (0x005) call 0600092A
In Compiler::impImportCall: opcode is call, kind=0, callRetType is ref, structSize is 0

    [ 1]  10 (0x00a) ldtoken
    [ 2]  15 (0x00f) call 0600092A
In Compiler::impImportCall: opcode is call, kind=0, callRetType is ref, structSize is 0

    [ 2]  20 (0x014) call 06000931
In Compiler::impImportCall: opcode is call, kind=0, callRetType is bool, structSize is 0
Importing Type.op_*Equality intrinsic

Folding call to Type:op_Equality to a simple compare via EQ
Optimizing compare of types-from-handles to instead compare handles
Asking runtime to compare 0000019B18EEA648 (Byte) and 0000019B18EEA648 (Byte) for equality
Runtime reports comparison is known at jit time: 1

    [ 1]  25 (0x019) brtrue.s
Folding operator with constant nodes into a constant:
               [000126] ------------              /--*  CNS_INT   int    0
               [000127] ------------              *  NE        int   
               [000125] ------------              \--*  CNS_INT   int    1
Bashed to int constant:
               [000127] ------------              *  CNS_INT   int    1

The conditional jump becomes an unconditional jump to BB21

impImportBlockPending for BB21

Importing BB21 (PC=054) of 'ArrayPool`1:get_Shared():ref'
    [ 0]  54 (0x036) ldsfld 0A00045C
    [ 1]  59 (0x03b) ret

    Inlinee Return expression (before normalization)  =>
               [000137] --CXG-------              *  IND       ref   
               [000135] ------------              |  /--*  CNS_INT   int    0 Fseq[s_specificPool]
               [000136] --CXG-------              \--*  ADD       byref 
               [000134] H-CXG-------                 \--*  CALL help byref  HELPER.CORINFO_HELP_GETSHARED_GCSTATIC_BASE_DYNAMICCLASS
               [000130] #----------- arg0               +--*  IND       long  
               [000129] ------------                    |  \--*  CNS_INT(h) long   0x19b18f3e7b0 cid/mid
               [000131] ------------ arg1               \--*  CNS_INT   int    58


               [000140] ------------              *  STMT      void  
               [000137] --CXG-------              |  /--*  IND       ref   
               [000135] ------------              |  |  |  /--*  CNS_INT   int    0 Fseq[s_specificPool]
               [000136] --CXG-------              |  |  \--*  ADD       byref 
               [000134] H-CXG-------              |  |     \--*  CALL help byref  HELPER.CORINFO_HELP_GETSHARED_GCSTATIC_BASE_DYNAMICCLASS
               [000130] #----------- arg0         |  |        +--*  IND       long  
               [000129] ------------              |  |        |  \--*  CNS_INT(h) long   0x19b18f3e7b0 cid/mid
               [000131] ------------ arg1         |  |        \--*  CNS_INT   int    58
               [000139] -ACXG-------              \--*  ASG       ref   
               [000138] D------N----                 \--*  LCL_VAR   ref    V08 tmp2         


    Inlinee Return expression (after normalization) =>
               [000141] ------------              *  LCL_VAR   ref    V08 tmp2         

After impImport() added block for try,catch,finally
--------------------------------------------------------------------------------------------------------------------------------------
BBnum BBid ref try hnd                 weight    [IL range]     [jump]      [EH region]         [flags]
--------------------------------------------------------------------------------------------------------------------------------------
BB19 [0016]  1                             1     [000..01B)-> BB21 (always)                     i 
BB20 [0017]  1                             1     [01B..036)-> BB22 ( cond )                     
BB21 [0018]  2                             1     [036..03C)        (return)                     i 
BB22 [0019]  1                             1     [03C..042)        (return)                     
--------------------------------------------------------------------------------------------------------------------------------------

BB20 was not imported, marked as removed (1)
BB22 was not imported, marked as removed (2)

Renumbering the basic blocks for fgRemoveEmptyBlocks

*************** Before renumbering the basic blocks

--------------------------------------------------------------------------------------------------------------------------------------
BBnum BBid ref try hnd                 weight    [IL range]     [jump]      [EH region]         [flags]
--------------------------------------------------------------------------------------------------------------------------------------
BB19 [0016]  1                             1     [000..01B)-> BB21 (always)                     i 
BB21 [0018]  2                             1     [036..03C)        (return)                     i 
--------------------------------------------------------------------------------------------------------------------------------------

***************  Exception Handling table
index  eTry, eHnd
  0  ::            - Try at BB02..BB07 [016..038), Finally at BB08..BB08 [038..04D)
Renumber BB19 to BB23
Renumber BB21 to BB24

*************** After renumbering the basic blocks

--------------------------------------------------------------------------------------------------------------------------------------
BBnum BBid ref try hnd                 weight    [IL range]     [jump]      [EH region]         [flags]
--------------------------------------------------------------------------------------------------------------------------------------
BB23 [0016]  1                             1     [000..01B)-> BB24 (always)                     i 
BB24 [0018]  2                             1     [036..03C)        (return)                     i 
--------------------------------------------------------------------------------------------------------------------------------------

***************  Exception Handling table
index  eTry, eHnd
  0  ::            - Try at BB02..BB07 [016..038), Finally at BB08..BB08 [038..04D)

New BlockSet epoch 1, # of blocks (including unused BB00): 1, bitset array size: 1 (short)


----------- Statements (and blocks) added due to the inlining of call [000080] -----------

Inlinee method body:New Basic Block BB25 [0020] created.
EH#0: New last block of handler: BB25

Convert bbJumpKind of BB24 to BBJ_NONE
fgInlineAppendStatements: no gc ref inline locals.

--------------------------------------------------------------------------------------------------------------------------------------
BBnum BBid ref try hnd                 weight    [IL range]     [jump]      [EH region]         [flags]
--------------------------------------------------------------------------------------------------------------------------------------
BB23 [0016]  1     0                       1     [040..041)-> BB24 (always)    H0               i 
BB24 [0018]  2     0                       1     [040..041)                    H0               i 
--------------------------------------------------------------------------------------------------------------------------------------

------------ BB23 [040..041) -> BB24 (always), preds={} succs={BB24}

------------ BB24 [040..041), preds={} succs={BB25}

***** BB24, stmt 1
               [000140] ------------              *  STMT      void  (IL 0x040...  ???)
               [000137] --CXG-------              |  /--*  IND       ref   
               [000135] ------------              |  |  |  /--*  CNS_INT   int    0 Fseq[s_specificPool]
               [000136] --CXG-------              |  |  \--*  ADD       byref 
               [000134] H-CXG-------              |  |     \--*  CALL help byref  HELPER.CORINFO_HELP_GETSHARED_GCSTATIC_BASE_DYNAMICCLASS
               [000130] #----------- arg0         |  |        +--*  IND       long  
               [000129] ------------              |  |        |  \--*  CNS_INT(h) long   0x19b18f3e7b0 cid/mid
               [000131] ------------ arg1         |  |        \--*  CNS_INT   int    58
               [000139] -ACXG-------              \--*  ASG       ref   
               [000138] D------N----                 \--*  LCL_VAR   ref    V08 tmp2         

-------------------------------------------------------------------------------------------------------------------

Return expression for call at [000080] is
               [000141] ------------              *  LCL_VAR   ref    V08 tmp2         
Successfully inlined ArrayPool`1:get_Shared():ref (66 IL bytes) (depth 1) [aggressive inline attribute]
--------------------------------------------------------------------------------------------

INLINER: during 'fgInline' result 'success' reason 'aggressive inline attribute' for 'Stream:CopyTo(ref,int):this' calling 'ArrayPool`1:get_Shared():ref'
INLINER: during 'fgInline' result 'success' reason 'aggressive inline attribute'

Replacing the return expression placeholder [000082] with [000141]
               [000082] --C---------              *  RET_EXPR  ref   (inl return from call [000141])

Inserting the inline return expression
               [000141] ------------              *  LCL_VAR   ref    V08 tmp2         

**** Late devirt opportunity
               [000085] --C-G-------              *  CALLV ind void   ArrayPool`1.Return
               [000141] ------------ this in rcx  +--*  LCL_VAR   ref    V08 tmp2         
               [000083] ------------ arg1         +--*  LCL_VAR   ref    V03 loc0         
               [000084] ------------ arg2         \--*  CNS_INT   int    0

impDevirtualizeCall: Trying to devirtualize virtual call:
    class for 'this' is ArrayPool`1 (attrib 20000400)
    base method is ArrayPool`1::Return
    devirt to ArrayPool`1::Return -- speculative
               [000085] --C-G-------              *  CALLV ind void   ArrayPool`1.Return
               [000141] ------------ this in rcx  +--*  LCL_VAR   ref    V08 tmp2         
               [000083] ------------ arg1         +--*  LCL_VAR   ref    V03 loc0         
               [000084] ------------ arg2         \--*  CNS_INT   int    0
    Class not final or exact, method not final, no devirtualization
*************** After fgInline()

@AndyAyersMS
Copy link
Member Author

This is now likely hitting the issue of extracting a type from a static field access. Because the static is in a generic type the static field expansion is complex and the jit doesn't see the type.

In one of your variants you had these static caches factored out into a non-generic helper class; that might work better here.

@benaadams
Copy link
Member

Beautiful 😄

**** Late devirt opportunity
               [000085] --C-G-------              *  CALLV ind void   ArrayPool`1.Return
               [000142] ------------ this in rcx  +--*  LCL_VAR   ref    V08 tmp2         
               [000083] ------------ arg1         +--*  LCL_VAR   ref    V03 loc0         
               [000084] ------------ arg2         \--*  CNS_INT   int    0

impDevirtualizeCall: Trying to devirtualize virtual call:
    class for 'this' is TlsOverPerCoreLockedStacksArrayPool`1 [final] (attrib 21000010)
    base method is ArrayPool`1::Return
    devirt to TlsOverPerCoreLockedStacksArrayPool`1::Return -- final class
               [000085] --C-G-------              *  CALLV ind void   ArrayPool`1.Return
               [000142] ------------ this in rcx  +--*  LCL_VAR   ref    V08 tmp2         
               [000083] ------------ arg1         +--*  LCL_VAR   ref    V03 loc0         
               [000084] ------------ arg2         \--*  CNS_INT   int    0
    final class; can devirtualize
... after devirt...
               [000085] --C-G-------              *  CALL nullcheck void   TlsOverPerCoreLockedStacksArrayPool`1.Return
               [000142] ------------ this in rcx  +--*  LCL_VAR   ref    V08 tmp2         
               [000083] ------------ arg1         +--*  LCL_VAR   ref    V03 loc0         
               [000084] ------------ arg2         \--*  CNS_INT   int    0
*************** After fgInline()

@benaadams
Copy link
Member

Doesn't always work; will see if I can get more info

       mov      rax, qword ptr [rax+64]
       call     gword ptr [rax+32]ArrayPool`1:Rent(int):ref:this
       mov      gword ptr [rbp-48H], rax
G_M32816_IG03:
; ...
G_M32816_IG13:
       mov      rdx, gword ptr [rbp-48H]
       xor      r8d, r8d
       cmp      dword ptr [rcx], ecx
       call     TlsOverPerCoreLockedStacksArrayPool`1:Return(ref,bool):this
       mov      eax, esi

@benaadams
Copy link
Member

1439 calls currently devirtualise in System.Private.CoreLib with the addition of this and #15743

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
4 participants