Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get exact class during devirtualization #45526

Merged
merged 5 commits into from
Dec 5, 2020

Conversation

AndyAyersMS
Copy link
Member

If we devirtualize to a method on a generic class, try and obtain the
exact class. Pass this back to the jit to unblock some types of inlines.

Also refactor how information is passed during devirtualization in
anticipation of follow on work to devirtualize default interface methods.
Because there are now multiple inputs and outputs, convey everthing
using a struct.

Resolves #38477.

If we devirtualize to a method on a generic class, try and obtain the
exact class. Pass this back to the jit to unblock some types of inlines.

Also refactor how information is passed during devirtualization in
anticipation of follow on work to devirtualize default interface methods.
Because there are now multiple inputs and outputs, convey everthing
using a struct.

Resolves dotnet#38477.
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Dec 3, 2020
@AndyAyersMS
Copy link
Member Author

@davidwrighton PTAL

cc @dotnet/jit-contrib @Rattenkrieg

@@ -9002,33 +9009,36 @@ CORINFO_METHOD_HANDLE CEEInfo::resolveVirtualMethodHelper(CORINFO_METHOD_HANDLE
Assembly* pCallerAssembly = callerMethod->GetModule()->GetAssembly();
bool allowDevirt =
IsInSameVersionBubble(pCallerAssembly , pDevirtMD->GetModule()->GetAssembly())
&& IsInSameVersionBubble(pCallerAssembly , pDerivedMT->GetAssembly());
&& IsInSameVersionBubble(pCallerAssembly, pExactMT->GetAssembly());
Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure if this change makes sense; for an instantiation which assembly is returned?

Assuming this does make sense, if the exact class would block devirt we might try checking again with the shared class...?

Copy link
Member

Choose a reason for hiding this comment

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

GetAssembly is not affected by instantiation, but this change loses track of the R2R protection. Instead of using pExactMT should be pObjMT

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, right. We need to check that the full inheritance chain is in the bubble.

@AndyAyersMS
Copy link
Member Author

Not many hits here per PMI. Could be something else is still blocking optimization in places.

Total bytes of delta: 357 (0.00% of base)
    diff is a regression.

Top file regressions (bytes):
         146 : System.Security.Cryptography.X509Certificates.dasm (0.09% of base)
         143 : System.ComponentModel.Composition.dasm (0.04% of base)
          64 : System.Windows.Extensions.dasm (0.41% of base)
          30 : FSharp.Core.dasm (0.00% of base)
          22 : Microsoft.VisualBasic.Core.dasm (0.00% of base)
           3 : System.Text.Json.dasm (0.00% of base)

Top file improvements (bytes):
         -36 : Microsoft.CodeAnalysis.VisualBasic.dasm (-0.00% of base)
         -15 : Microsoft.CodeAnalysis.CSharp.dasm (-0.00% of base)

8 total files with Code Size differences (2 improved, 6 regressed), 260 unchanged.

Top method regressions (bytes):
         132 ( 1.97% of base) : System.ComponentModel.Composition.dasm - ExportProvider:GetExportsCore(String):IEnumerable`1:this (14 methods)
          64 (12.70% of base) : System.Windows.Extensions.dasm - X509Certificate2UI:SelectFromCollectionHelper(X509Certificate2Collection,String,String,int,long):X509Certificate2Collection
          38 ( 3.71% of base) : System.Security.Cryptography.X509Certificates.dasm - ChainPal:BuildChain(bool,ICertificatePal,X509Certificate2Collection,OidCollection,OidCollection,int,int,X509Certificate2Collection,int,DateTime,TimeSpan,bool):ChainPal
          36 ( 5.79% of base) : System.Security.Cryptography.X509Certificates.dasm - CertificatePal:FilterPFXStore(ReadOnlySpan`1,SafePasswordHandle,int):SafeCertContextHandle
          32 (10.19% of base) : System.Security.Cryptography.X509Certificates.dasm - ChainPal:GetChainEngine(int,X509Certificate2Collection,bool):SafeChainEngineHandle
          30 (16.39% of base) : FSharp.Core.dasm - StringModule:Concat(String,IEnumerable`1):String
          30 ( 6.20% of base) : System.Security.Cryptography.X509Certificates.dasm - FindPal:FindCore(int,long,Func`2):this
          17 ( 2.60% of base) : System.Security.Cryptography.X509Certificates.dasm - StorePal:FromBlobOrFile(ReadOnlySpan`1,String,SafePasswordHandle,int):StorePal
          15 (23.81% of base) : System.Security.Cryptography.X509Certificates.dasm - FindPal:Dispose():this
          15 (25.42% of base) : System.Security.Cryptography.X509Certificates.dasm - StorePal:Dispose():this
          15 (20.55% of base) : System.Security.Cryptography.X509Certificates.dasm - StorePal:MoveTo(X509Certificate2Collection):this
          11 ( 4.07% of base) : Microsoft.VisualBasic.Core.dasm - FileSystem:get_Drives():ReadOnlyCollection`1
          11 ( 1.95% of base) : Microsoft.VisualBasic.Core.dasm - FileSystem:FindInFiles(String,String,bool,int,ref):ReadOnlyCollection`1
          11 ( 2.22% of base) : System.ComponentModel.Composition.dasm - ExportProvider:GetExports(Type,Type,String):IEnumerable`1:this
           3 ( 9.09% of base) : System.Text.Json.dasm - ObjectConverter:ReadWithQuotes(byref):Object:this

Top method improvements (bytes):
         -52 (-5.65% of base) : System.Security.Cryptography.X509Certificates.dasm - CertificatePal:FromBlobOrFile(ReadOnlySpan`1,String,SafePasswordHandle,int):ICertificatePal
         -21 (-23.33% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - EmbeddedTypesManager:VerifyNotFrozen():this
         -15 (-1.51% of base) : Microsoft.CodeAnalysis.CSharp.dasm - PEModuleBuilder:ValidateReferencedAssembly(AssemblySymbol,AssemblyReference,DiagnosticBag):this
         -15 (-1.20% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - PEModuleBuilder:ValidateReferencedAssembly(AssemblySymbol,AssemblyReference,DiagnosticBag):this

Top method regressions (percentages):
          15 (25.42% of base) : System.Security.Cryptography.X509Certificates.dasm - StorePal:Dispose():this
          15 (23.81% of base) : System.Security.Cryptography.X509Certificates.dasm - FindPal:Dispose():this
          15 (20.55% of base) : System.Security.Cryptography.X509Certificates.dasm - StorePal:MoveTo(X509Certificate2Collection):this
          30 (16.39% of base) : FSharp.Core.dasm - StringModule:Concat(String,IEnumerable`1):String
          64 (12.70% of base) : System.Windows.Extensions.dasm - X509Certificate2UI:SelectFromCollectionHelper(X509Certificate2Collection,String,String,int,long):X509Certificate2Collection
          32 (10.19% of base) : System.Security.Cryptography.X509Certificates.dasm - ChainPal:GetChainEngine(int,X509Certificate2Collection,bool):SafeChainEngineHandle
           3 ( 9.09% of base) : System.Text.Json.dasm - ObjectConverter:ReadWithQuotes(byref):Object:this
          30 ( 6.20% of base) : System.Security.Cryptography.X509Certificates.dasm - FindPal:FindCore(int,long,Func`2):this
          36 ( 5.79% of base) : System.Security.Cryptography.X509Certificates.dasm - CertificatePal:FilterPFXStore(ReadOnlySpan`1,SafePasswordHandle,int):SafeCertContextHandle
          11 ( 4.07% of base) : Microsoft.VisualBasic.Core.dasm - FileSystem:get_Drives():ReadOnlyCollection`1
          38 ( 3.71% of base) : System.Security.Cryptography.X509Certificates.dasm - ChainPal:BuildChain(bool,ICertificatePal,X509Certificate2Collection,OidCollection,OidCollection,int,int,X509Certificate2Collection,int,DateTime,TimeSpan,bool):ChainPal
          17 ( 2.60% of base) : System.Security.Cryptography.X509Certificates.dasm - StorePal:FromBlobOrFile(ReadOnlySpan`1,String,SafePasswordHandle,int):StorePal
          11 ( 2.22% of base) : System.ComponentModel.Composition.dasm - ExportProvider:GetExports(Type,Type,String):IEnumerable`1:this
         132 ( 1.97% of base) : System.ComponentModel.Composition.dasm - ExportProvider:GetExportsCore(String):IEnumerable`1:this (14 methods)
          11 ( 1.95% of base) : Microsoft.VisualBasic.Core.dasm - FileSystem:FindInFiles(String,String,bool,int,ref):ReadOnlyCollection`1

Top method improvements (percentages):
         -21 (-23.33% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - EmbeddedTypesManager:VerifyNotFrozen():this
         -52 (-5.65% of base) : System.Security.Cryptography.X509Certificates.dasm - CertificatePal:FromBlobOrFile(ReadOnlySpan`1,String,SafePasswordHandle,int):ICertificatePal
         -15 (-1.51% of base) : Microsoft.CodeAnalysis.CSharp.dasm - PEModuleBuilder:ValidateReferencedAssembly(AssemblySymbol,AssemblyReference,DiagnosticBag):this
         -15 (-1.20% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - PEModuleBuilder:ValidateReferencedAssembly(AssemblySymbol,AssemblyReference,DiagnosticBag):this

19 total methods with Code Size differences (4 improved, 15 regressed), 258813 unchanged.

Fixes the case from #38477, we now produce:

; Assembly listing for method Program:Main():int
; Emitting BLENDED_CODE for X64 CPU with AVX - Windows
; optimized code
; rsp based frame
; partially interruptible
; Final local variable assignments
;
;  V00 OutArgs      [V00    ] (  1,  1   )  lclBlk (32) [rsp+0x00]   "OutgoingArgSpace"
;* V01 tmp1         [V01,T00] (  0,  0   )     ref  ->  zero-ref    class-hnd exact "NewObj constructor temp"
;
; Lcl frame size = 40

G_M24375_IG01:
       sub      rsp, 40
						;; bbWeight=1    PerfScore 0.25
G_M24375_IG02:
       mov      eax, 1
						;; bbWeight=1    PerfScore 0.25
G_M24375_IG03:
       add      rsp, 40
       ret      
						;; bbWeight=1    PerfScore 1.25

; Total bytes of code 14, prolog size 4, PerfScore 3.15, instruction count 4 (MethodHash=d9b8a0c8) for method Program:Main():int

Also want to look into why the jit thinks it needs to create a frame.

@AndyAyersMS
Copy link
Member Author

Also want to look into why the jit thinks it needs to create a frame.

This happens because there's a newobj that we dead code, but only after lower, so the jit still thinks the method is going to make a call, and so allocates 40 bytes (8 to align the SP, and 32 for the outgoing args area).

I'm surprised we don't eliminate dead code earlier, when we first build liveness. Seems like it might pay for itself in terms of TP.

@AndyAyersMS
Copy link
Member Author

I'm surprised we don't eliminate dead code earlier, when we first build liveness.

And this happens because at that point there's a use for the newobj result -- a null check. So it appears to be alive.

***** BB01
STMT00000 (IL 0x000...0x00A)
N005 ( 16, 16) [000003] -AC-----R---              *  ASG       ref   
N004 (  1,  1) [000002] D------N----              +--*  LCL_VAR   ref    V01 tmp1         
N003 ( 16, 16) [000001] --C---------              \--*  CALL help ref    HELPER.CORINFO_HELP_NEWSFAST
N002 (  2, 10) [000000] H----------- arg0 in rcx     \--*  CNS_INT(h) long   0x7ffab24525c8 token

***** BB01
STMT00007 (IL 0x005...  ???)
N002 (  2,  2) [000028] ---X--------              *  NULLCHECK byte  
N001 (  1,  1) [000006] ------------              \--*  LCL_VAR   ref    V01 tmp1         

***** BB01
STMT00004 (IL 0x00E...0x00F)
N002 (  2,  2) [000013] ------------              *  RETURN    int   
N001 (  1,  1) [000012] ------------              \--*  CNS_INT   int    1

So why can't local assertion prop get rid of this nullcheck? Looks like it does not know the newobj can't produce a null result. Ironically the nullcheck produces a non-null assertion...

fgMorphTree BB01, STMT00000 (after)
               [000003] -AC--+------              *  ASG       ref   
               [000002] D----+-N----              +--*  LCL_VAR   ref    V01 tmp1         
               [000001] --C--+------              \--*  CALL help ref    HELPER.CORINFO_HELP_NEWSFAST
               [000000] H----+------ arg0 in rcx     \--*  CNS_INT(h) long   0x7ffab24525c8 token

fgMorphTree BB01, STMT00007 (before)
               [000028] ---X--------              *  NULLCHECK byte  
               [000006] ------------              \--*  LCL_VAR   ref    V01 tmp1         
GenTreeNode creates assertion:
               [000028] ---X--------              *  NULLCHECK byte  
In BB01 New Local Constant Assertion: V01 != null index=#01, mask=0000000000000001

fgMorphTree BB01, STMT00003 (before)
               [000011] ------------              *  NOP       void  


fgMorphTree BB01, STMT00004 (before)
               [000013] ------------              *  RETURN    int   
               [000012] ------------              \--*  CNS_INT   int    1

//
public CORINFO_METHOD_STRUCT_* devirtualizedMethod;
public uint _requiresInstMethodTableArg;
public bool requiresInstMethodTableArg { get { return _requiresInstMethodTableArg != 0; } set { _requiresInstMethodTableArg = value ? (byte)1 : (byte)0; } }
Copy link
Contributor

Choose a reason for hiding this comment

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

There is some entropy wrt passing bool flags between managed and unmanaged code in this file. So far I see uints and bytes used. You probably copied that pattern from CORINFO_CALL_INFO above which uses Windows BOOL on native side. Other structs defined in corinfo.h have fields declared as c++ bool.

Copy link
Member Author

Choose a reason for hiding this comment

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

I noticed -- @davidwrighton can you comment on that?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, bool is byte in managed code and BOOL is uint in managed code. (And to be pedantic, VARIANT_BOOL is ushort in managed code, but just don't use that type.) Due to the various alignment rules in play the code as written will function correctly on our current set of platforms, but please change the field type to byte.

@Rattenkrieg is completely correct. In a perfect world we'd standardize on bool. In fact, I'd like to see us change our c++ headers so that they are compatible with a standard set of C++ headers instead of requiring the PAL headers, but that's a much larger change that I may attempt over the holidays.

// interface corresponding to pBaseMD.
if (!pDerivedMT->CanCastToInterface(pBaseMT))
if (!pObjMT->CanCastToInterface(pBaseMT))
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps this PR could be a better place to introduce/evaluate this change

Copy link
Member Author

Choose a reason for hiding this comment

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

We can do that by amending your PR, or as a subsequent PR.

Hopefully what I've done here sets things up so that change is more straightforward.

@AndyAyersMS
Copy link
Member Author

One test failure to investigate...

@Rattenkrieg
Copy link
Contributor

One test failure to investigate...

I guess this one

Regressions/coreclr/15650/interfacecctor/interfacecctor.sh [FAIL]

dotnet/coreclr#16340
I've also stumbled upon this. I don't have access to my home machine atm but I remember that resulting assembly was like

   mov      eax, 0      ; s_result
   ret

my guess that we should not inline constructor body when class has beforefieldinit.

@AndyAyersMS
Copy link
Member Author

The bug was that we were failing to find the exact context because for DIMs the class can't be found by searching the inheritance chain. So the exact context was null and the context value 1 (created from null by MAKE_CLASSCONTEXT) has a special meaning which caused the jit to bypass the class init check.

Now when we have that case we will just use the class associated with the method...

@Rattenkrieg
Copy link
Contributor

@AndyAyersMS how did you managed to have jit diffs while having changes on vm side (coreclr.dll)? See very last line of this comment Do you run a patched jit-diff like I do, or is there a way to link coreclr lib statically?

@AndyAyersMS
Copy link
Member Author

I run jit-diffs twice, one for the base and one for the diff, and then jit-analyze to compare the two...

@AndyAyersMS
Copy link
Member Author

AndyAyersMS commented Dec 3, 2020

The bug was that we were failing to find the exact context

Likely need to make a matching fix in crossgen2...

[edit: actually I think crossgen2 is ok for now because FindMethodOnTypeWithMatchingTypicalMethod checks for the non-generic case up front, so it won't walk the hierarchy].

@AndyAyersMS
Copy link
Member Author

Also want to look into why the jit thinks it needs to create a frame.
...
Looks like it does not know the newobj can't produce a null result

I have prototyped a fix where the jit will no longer create a partial frame (master...AndyAyersMS:NonNullNewAssertion). However, like many assertion prop changes, it also has a downside because it may eliminate other useful assertions. Overall jit-diff impact is small so probably not worth pursuing; per PMI:

16 total methods with Code Size differences (10 improved, 6 regressed), 258816 unchanged.

It might be more surgical to fix this problem by resetting the outgoing arg size accounting before running lower, but again that probably has minimal impact.

@AndyAyersMS
Copy link
Member Author

@davidwrighton any chance you can look this over?

Copy link
Member

@davidwrighton davidwrighton left a comment

Choose a reason for hiding this comment

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

The R2R and bool changes are needed, but otherwise I like the change.

//
public CORINFO_METHOD_STRUCT_* devirtualizedMethod;
public uint _requiresInstMethodTableArg;
public bool requiresInstMethodTableArg { get { return _requiresInstMethodTableArg != 0; } set { _requiresInstMethodTableArg = value ? (byte)1 : (byte)0; } }
Copy link
Member

Choose a reason for hiding this comment

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

Yes, bool is byte in managed code and BOOL is uint in managed code. (And to be pedantic, VARIANT_BOOL is ushort in managed code, but just don't use that type.) Due to the various alignment rules in play the code as written will function correctly on our current set of platforms, but please change the field type to byte.

@Rattenkrieg is completely correct. In a perfect world we'd standardize on bool. In fact, I'd like to see us change our c++ headers so that they are compatible with a standard set of C++ headers instead of requiring the PAL headers, but that's a much larger change that I may attempt over the holidays.

@@ -9002,33 +9009,36 @@ CORINFO_METHOD_HANDLE CEEInfo::resolveVirtualMethodHelper(CORINFO_METHOD_HANDLE
Assembly* pCallerAssembly = callerMethod->GetModule()->GetAssembly();
bool allowDevirt =
IsInSameVersionBubble(pCallerAssembly , pDevirtMD->GetModule()->GetAssembly())
&& IsInSameVersionBubble(pCallerAssembly , pDerivedMT->GetAssembly());
&& IsInSameVersionBubble(pCallerAssembly, pExactMT->GetAssembly());
Copy link
Member

Choose a reason for hiding this comment

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

GetAssembly is not affected by instantiation, but this change loses track of the R2R protection. Instead of using pExactMT should be pObjMT

@AndyAyersMS AndyAyersMS added this to Needs Triage in .NET Core CodeGen via automation Dec 5, 2020
@AndyAyersMS AndyAyersMS moved this from Needs Triage to In progress in .NET Core CodeGen Dec 5, 2020
@AndyAyersMS AndyAyersMS merged commit ea35f6a into dotnet:master Dec 5, 2020
.NET Core CodeGen automation moved this from In progress to Done Dec 5, 2020
@AndyAyersMS AndyAyersMS deleted the ExactContextAfterDevirt branch December 5, 2020 16:33
@ghost ghost locked as resolved and limited conversation to collaborators Jan 4, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Compute precise generic context after de-virtualization
4 participants