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

Instantiating stub for devirtualized default interface method on a generic type #43668

Conversation

Rattenkrieg
Copy link
Contributor

Fixes #9588
I don't believe this could be that easy, otherwise why this wasn't added in dotnet/coreclr#15979 🤔
I'm struggling to find jit asm changes at the moment.

./jit-diff diff --output /tmp/jit-diffs \
--core_root ~/projs/runtime/artifacts/tests/coreclr/Linux.x64.Release/Tests/Core_Root \
--base ~/projs/runtime/artifacts/bin/coreclr/Linux.x64.Release \
--diff ~/projs/runtime/artifacts/bin/coreclr/Linux.x64.Release.patched \
--arch x64 --crossgen ~/projs/runtime/artifacts/tests/coreclr/Linux.x64.Release/Tests/Core_Root/crossgen \
--tests --test_root ~/projs/runtime/src/tests/

produces no diff, I've also tried with --framework and --corelib.
And I've also compared jit output from the following command

COMPlus_JitDump=Main COMPlus_JitDisasm=Main CORE_ROOT=~/projs/runtime/artifacts/tests/coreclr/Linux.x64.Debug/Tests/Core_Root \
~/projs/runtime/artifacts/tests/coreclr/Linux.x64.Release/XXX

where XXX were diamondshape_r.ilproj, non_virtual_calls_to_instance_methods.ilproj and sharedgenerics_d.ilproj and new project I created with the code from #39419
... still no difference.

So my thoughts are: either I've missed some optimization flag or messed with setup in any other way OR we never reaching that code 🤷
Going to try tomorrow on Windows.

@MichalStrehovsky PTAL as you were the author of dotnet/coreclr#15979 and friends.

@MichalStrehovsky
Copy link
Member

@MichalStrehovsky PTAL as you were the author of dotnet/coreclr#15979 and friends.

Let me know if you have trouble extracting the failing tests from the CI. E.g. here is one:

    Loader\classloader\DefaultInterfaceMethods\constrainedcall\constrainedcall\constrainedcall.cmd [FAIL]
      
      Assert failure(PID 2016 [0x000007e0], Thread: 2844 [0x0b1c]): false
      
      CORECLR! MethodDesc::FindOrCreateAssociatedMethodDesc + 0x1F9E (0x00007ff8`1658dc06)
      CORECLR! CEEInfo::resolveVirtualMethodHelper + 0x350 (0x00007ff8`163b102c)
      CORECLR! CEEInfo::resolveVirtualMethod + 0x17C (0x00007ff8`163b0c5c)
      CLRJIT! Compiler::impDevirtualizeCall + 0x5A2 (0x00007ff8`15585606)
      CLRJIT! Compiler::impImportCall + 0x1E68 (0x00007ff8`155931d0)
      CLRJIT! Compiler::impImportBlockCode + 0x45AE (0x00007ff8`1558c9be)
      CLRJIT! Compiler::impImportBlock + 0xD3 (0x00007ff8`15587b53)
      CLRJIT! Compiler::impImport + 0x39F (0x00007ff8`1558727f)
      CLRJIT! Compiler::fgImport + 0x17 (0x00007ff8`15543237)
      CLRJIT! Phase::Run + 0x3B (0x00007ff8`15622f3b)
          File: F:\workspace\_work\1\s\src\coreclr\src\vm\genmeth.cpp Line: 811
          Image: C:\h\w\A23308C7\p\CoreRun.exe

I don't have pointers for you - I've decided the CoreCLR type system is not something I want to be involved in after finishing the default interface methods feature and haven't looked back since.

@Rattenkrieg
Copy link
Contributor Author

Let me know if you have trouble extracting the failing tests from the CI. E.g. here is one:

Thank you! I've managed to dig through pipelines but I'm struggling to reproduce that failure.
What I did:

# to have clrjit and coreclr libs with assertions enabled
> .\build.cmd clr+libs -rc checked -c checked -lc debug

# otherwise tests won't be built
> .\build.cmd clr+libs -rc release -c release -lc release

> .\src\tests\build.cmd checked

# run all tests just in case there are other failures
> .\src\tests\run.cmd checked

# at this point everything fine except couple of bencharks like from benchmarkgame

# trying to reproduce Loader\classloader\DefaultInterfaceMethods\constrainedcall\constrainedcall\constrainedcall.cmd failure
> $env:CORE_ROOT=".\runtime\artifacts\tests\coreclr\Windows_NT.x64.Checked\Tests\Core_Root"
> .\artifacts\tests\coreclr\Windows_NT.x64.Checked\Loader\classloader\DefaultInterfaceMethods\constrainedcall\constrainedcall\constrainedcall.cmd

... and it passes

BEGIN EXECUTION
 "C:\Users\sergey\source\repos\runtime\artifacts\tests\coreclr\Windows_NT.x64.Checked\Tests\Core_Root\corerun.exe" constrainedcall.dll
Constrained calls that require runtime lookup are OK
Runtime does not support lookups with runtime determined boxing
Compile time constraint resolution to default interface method OK
Expected: 100
Actual: 100
END EXECUTION - PASSED
PASSED

🤯

I see there is setup being done during pipeline builds:

C:\h\w\A1A808CD\w\AB4A0904\e>set __TestEnv=C:\h\w\A1A808CD\w\AB4A0904\u\SetStressModes_no_tiered_compilation.cmd

I can't find that file in my repo/artifacts. Could there be some crucial setup steps like setting COMPlus_MakeThisFailForSure etc?

@MichalStrehovsky
Copy link
Member

like setting COMPlus_MakeThisFailForSure etc?

:) Try COMPlus_TieredCompilation=0

@Rattenkrieg
Copy link
Contributor Author

Thank you @MichalStrehovsky ❤️ now I'm able to reproduce and have couple of failing tests under /regeressions dir. And all that issues have linked PR's submitted by you. Reading through related discussions I can feel your PTSD

I've decided the CoreCLR type system is not something I want to be involved in after finishing the default interface methods feature and haven't looked back since.

Copy link
Contributor Author

@Rattenkrieg Rattenkrieg left a comment

Choose a reason for hiding this comment

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

I'm done with general logic changes. The gist is:
resolveVirtualMethodHelper now returns wrapped MethodDesc for devirtualized default interface method.

interface I<T>
{
    string DefaultTypeOf() => typeof(T).Name;
}

class C : I<string>
{
}

public static class Program
{
    [MethodImpl(MethodImplOptions.AggressiveOptimization)]
    static int Main()
    {
        var c = new C();
        var dcs = ((I<string>)c).DefaultTypeOf();
        if (dcs != "String") return 200;
        return 100;
    }
}

For the program above we will get I'1[[System.String]][System.String].DefaultTypeOf from pDerivedMT->GetMethodDescForInterfaceMethod(TypeHandle(pOwnerMT), pBaseMD, FALSE); Which is already an instantiating stub!
Then we going to unwrap it into I'1[[System.__Canon]][System.String].DefaultTypeOf, tell to caller that *requiresInstMethodTableArg = true and do our business in impDevirtualizeCall:
Get the instantiation param from context: GenTree* instParam = gtNewIconEmbClsHndNode(exactClassHandle);. Context here is I<string> part of ((I<string>)c).DefaultTypeOf();. Pass that param, so we will end with:

               [000010] I-C-G-------              *  CALL nullcheck ref    I`1[__Canon][System.__Canon].DefaultTypeOf (exactContextHnd=0x00007FF95AE1FE50)
               [000009] ------------ this in rcx  +--*  LCL_VAR   ref    V00 loc0
               [000011] H----------- arg1         \--*  CNS_INT(h) long   0x7ff95ae40020 class   
                                                                          ^--  I`1[System.String] type handle

and the following assembly would be

IN0002: 00000F call     CORINFO_HELP_NEWSFAST
IN0003: 000014 mov      rsi, rax
IN0004: 000017 mov      rcx, rsi
IN0005: 00001A call     System.Object:.ctor():this
IN0006: 00001F mov      rcx, rsi
IN0007: 000022 mov      rdx, 0x7FF95AE40020                                                                            # I`1[System.String] type handle
IN0008: 00002C call     I`1[__Canon][System.__Canon]:DefaultTypeOf():System.String:this

src/coreclr/src/inc/corinfo.h Outdated Show resolved Hide resolved
src/coreclr/src/jit/ICorJitInfo_API_wrapper.hpp Outdated Show resolved Hide resolved
src/coreclr/src/jit/importer.cpp Outdated Show resolved Hide resolved
@@ -581,7 +581,7 @@ class MethodDesc
//
// RequiresInstMethodDescArg()
// The method is itself generic and is shared between generic
// instantiations but is not itself generic. Furthermore
// instantiations but is not itself generic. WTF LOL. Furthermore
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I forgot to change wording before pushing.
I think we should rephrase that

The method is itself generic and is shared between generic instantiations but is not itself generic

if (!pDerivedMT->CanCastToInterface(pBaseMT))
{
return nullptr;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've delayed this check because it forbids several devirt oportunities:

interface I<T>
{
    string DefaultTypeOf() => typeof(T).Name;
}

class C : I<string>
{
}

public static class Program
{
    [MethodImpl(MethodImplOptions.AggressiveOptimization)]
    static int Main()
    {
        var c = new C();
        var dcs = ((I<string>)c).DefaultTypeOf();
        if (dcs != "String") return 200;
        return 100;
    }
}

The check above makes us return nullptr for ((I<string>)c).DefaultTypeOf(); Since it takes !pTargetMT->HasVariance() branch and we fail to comapre I<String> with I<__Canon> .

https://github.com/dotnet/runtime/blob/dfc8da5dd778c6b165e6be9e7c16c82ebf571bfb/src/coreclr/src/vm/methodtable.inl#L1543

So this change should allow us to devirtualize cases like class C<T> : I<T> and class C : I<string>.
By the way cases like class C : I<int> are fine since we comparing I<Int32> with I<Int32>

@Rattenkrieg
Copy link
Contributor Author

I wonder what could be the reason behind inliner failure

impDevirtualizeCall: Trying to devirtualize interface call:
    class for 'this' is C [exact] (attrib 20000000)
    base method is I`1[__Canon]::DefaultTypeOf
--- base class is interface
    devirt to I`1[[System.Object, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]][System.Object]::DefaultTypeOf -- exact
               [000010] --C-G-------              *  CALLV stub ref    I`1[__Canon][System.__Canon].DefaultTypeOf
               [000009] ------------ this in rcx  \--*  LCL_VAR   ref    V00 loc0
    exact; can devirtualize
... after devirt...
               [000010] --C-G-------              *  CALL nullcheck ref    I`1[[System.Object, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]][System.Object].DefaultTypeOf
               [000009] ------------ this in rcx  \--*  LCL_VAR   ref    V00 loc0
INLINER: during 'impMarkInlineCandidate' result 'failed this callee' reason 'cannot get method info' for 'Program:Main():int' calling 'I`1[[System.Object, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]][System.Object]:DefaultTypeOf():System.String:this'

INLINER: Marking I`1[[System.Object, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]][System.Object]:DefaultTypeOf():System.String:this as NOINLINE because of cannot get method info
INLINER: during 'impMarkInlineCandidate' result 'failed this callee' reason 'cannot get method info'

@Rattenkrieg
Copy link
Contributor Author

Rattenkrieg commented Oct 27, 2020

BTW all failures in checked builds are happening here

PRECONDITION(IsArray() || ImplementsEquivalentInterface(ownerType.GetMethodTable()) || ownerType.GetMethodTable()->HasVariance());

This is because of this reordering
https://github.com/dotnet/runtime/pull/43668/files#r512813024
I'm investigating.

EDIT:

It appears I've messed with conditionals. Fixed in 61a7aab

@davidwrighton
Copy link
Member

Given the current state of what can be done with devirtualization in the JIT I worry that directly calling an instantiating stub may not provide much of a performance win. We'll need at least some level of performance benchmarking to indicate how much improvement this can provide.

@Rattenkrieg
Copy link
Contributor Author

Rattenkrieg commented Oct 30, 2020

@davidwrighton I've named this PR in consistent way with the problem described in #9588 After spending some time on this issue it appears the title does not describe preciselly what have been done in this PR. Let me briefly explain my understaing of "instantiating/unboxing stub" first: this is purely VM concept, there is no common asm counterpart of this on the compiler side, like the ones we call "PrecodeFixup", "PreStub" etc. It is the duty of the one who generates the code to emit all necessary instructions once they have been told by VM side that CORINFO_METHOD_HANDLE they dealing with is actually an instantiating/unboxing stub. You could see such contract being implemented in this PR betwen https://github.com/dotnet/runtime/blob/25f5d6b35a323c4124927aada8d87e416c501e16/src/coreclr/src/vm/jitinterface.cpp#L8963-L8971 and https://github.com/dotnet/runtime/blob/25f5d6b35a323c4124927aada8d87e416c501e16/src/coreclr/src/jit/importer.cpp#L20822

I would really love to hear your criticism on stated above.

For performance sake we are trading additional instruction for storing type handle in register * for eliminated virtual stub dispatch and potential inlining. Consider following c# code:

  • EDIT: it appears there is no regression wrt additional instructions and register allocation. For nondevirtualized case there would be a store of virtual stub in r11 introduced at morph stage, see
    if (call->IsVirtualStub())
interface I<T>
{
    string DefaultTypeOf() => typeof(T).Name;
}

class C : I<string>
{
}

public static class Program
{
    [MethodImpl(MethodImplOptions.AggressiveOptimization)]
    static int Main()
    {
        I<string> c = new C();
        var dcs = c.DefaultTypeOf();
        if (dcs != "String") return 200;
        return 100;
    }
}

when jitted relevant parts of Main would be:

00007FFEFA6AC33A  call        00007FFEFA690088                   # C.ctor()
00007FFEFA6AC33F  mov         rcx,rsi  
00007FFEFA6AC342  mov         rdx,7FFEFAA10860h                  # I`1[[System.String]] type handle
00007FFEFA6AC34C  call        00007FFEFA6ABDB8                   # I`1[__Canon][System.__Canon]:DefaultTypeOf()
00007FFEFA6AC351  mov         rcx,rax  
00007FFEFA6AC354  mov         rdx,1DD3FD031A8h  
00007FFEFA6AC35E  mov         rdx,qword ptr [rdx]  
00007FFEFA6AC361  call        00007FFEFA695978                   # String equals
00007FFEFA6AC366  test        eax,eax  
00007FFEFA6AC368  je          00007FFEFA6AC375  
00007FFEFA6AC36A  mov         eax,0C8h  
00007FFEFA6AC36F  add         rsp,20h  
00007FFEFA6AC373  pop         rsi  
00007FFEFA6AC374  ret  

00007FFEFA6ABDB8 initially would contain

00007FFEFA6ABDB8  call        PrecodeFixupThunk (07FFF594EEAD0h)  

and after jitting of DefaultTypeOf() just a jmp to actual code

00007FFEFA6ABDB8  jmp         00007FFEFA6AC3D0  

If we replace interface with class with virtual method like that:

class I<T>
{
    public virtual string DefaultTypeOf() => typeof(T).Name;
}

the only difference (in case of devirt) would be the absence of type handle store in rdx (we didn't tell compiler to emit it, because methodtable of C is sufficient) and direct call replacement with indirect (tbh I don't know what mechanism drives that and why this is the virtual call convention):

00007FFEFA68C36F  call        qword ptr [7FFEFA9F0730h] 

For me this is clearly better than VSD in non-devirtualized interface case and just one mov worse than devirtualized superclass case.

In given example we were not able to inline due to limitations mentioned in #38477 But if we try something like that:

interface I<T>
{
    T GetAt(int i, T[] tx) => tx[i];
}

class C : I<string>
{
}

public static class Program
{
    private static string[] tx = new string[] { "test" };

    [MethodImpl(MethodImplOptions.AggressiveOptimization)]
    static int Main()
    {
        I<string> c = new C();
        var dcs = c.GetAt(0, new string[] { "test" });
        if (dcs != "test") return 200;
        return 100;
    }
}

we will get inlined code

IN0001: 000005 mov      rcx, 0x7FFEFA9F0858
IN0002: 00000F call     CORINFO_HELP_NEWSFAST
IN0003: 000014 mov      rcx, rax
IN0004: 000017 call     System.Object:.ctor():this
IN0005: 00001C mov      rcx, 0x7FFEFA9618E0
IN0006: 000026 mov      edx, 1
IN0007: 00002B call     CORINFO_HELP_NEWARR_1_OBJ
IN0008: 000030 lea      rcx, bword ptr [rax+16]
IN0009: 000034 mov      rdx, 0x284C25431A8
IN000a: 00003E mov      rsi, gword ptr [rdx]
IN000b: 000041 mov      rdx, rsi
IN000c: 000044 call     CORINFO_HELP_ASSIGN_REF
IN000d: 000049 mov      rcx, rsi
IN000e: 00004C mov      rdx, rsi
IN000f: 00004F call     System.String:op_Inequality(System.String,System.String):bool
IN0010: 000054 test     eax, eax
IN0011: 000056 je       SHORT G_M24375_IG05

I'm totally ok if you still not convinced: would synthetic BDN harness be suffictient to demonstrate improvement or do I need to hack something like stuff described here then?

@Rattenkrieg
Copy link
Contributor Author

Rattenkrieg commented Oct 31, 2020

Some asm diff stats:
> .\jitutils\bin\jit-diff diff --output c:\diffs --core_root <...>\Windows_NT.x64.Debug\Tests\Core_Root --diff <...>\patched\Windows_NT.x64.Debug\ --base <...>\artifacts\bin\coreclr\Windows_NT.x64.Debug\ --pmi --tests --test_root <...>\artifacts\tests\coreclr\Windows_NT.x64.Release\Loader\classloader\DefaultInterfaceMethods\

Top file improvements (bytes):
        -111 : devirttest39419.dasm (-75.00% of base)
         -55 : constrainedcall\constrained3\constrained3.dasm (-49.11% of base)
         -14 : devirttestinline\devirttestinline\devirttestinline.dasm (-6.17% of base)
        ... rest is the noise from my debug c# code

devirttestinline is the example given in the end of my previous message, constrained3 is here https://github.com/dotnet/runtime/blob/master/src/tests/Loader/classloader/DefaultInterfaceMethods/constrainedcall/constrained3.il DIMs were inlined and affected by other optimizations like constant folding:

IN0008: 000000 sub      rsp, 40
IN0009: 000004 xor      rax, rax
IN000a: 000006 mov      qword ptr [V00 rsp+20H], rax

G_M29659_IG02:        ; offs=00000BH, size=0029H, bbWeight=1    PerfScore 6.75, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref

IN0001: 00000B mov      rcx, 0x13F900031A8                                        # "IFrobber<T>:Frob"
IN0002: 000015 mov      rcx, gword ptr [rcx]
IN0003: 000018 call     System.Console:WriteLine(System.String)
IN0004: 00001D mov      rcx, 0x13F900031B0                                        # "IRobber<T>:Frob"
IN0005: 000027 mov      rcx, gword ptr [rcx]
IN0006: 00002A call     System.Console:WriteLine(System.String)
IN0007: 00002F mov      eax, 100                                                  # 34 + 66

G_M29659_IG03:        ; offs=000034H, size=0005H, bbWeight=1    PerfScore 1.25, epilog, nogc, extend

IN000b: 000034 add      rsp, 40
IN000c: 000038 ret

devirttest39419 is example from #39419:

IN0005: 000000 sub      rsp, 56
IN0006: 000004 xor      rax, rax
IN0007: 000006 mov      qword ptr [V05 rsp+28H], rax
IN0008: 00000B mov      qword ptr [V05+0x8 rsp+30H], rax

G_M27646_IG02:        ; offs=000010H, size=0010H, bbWeight=1    PerfScore 2.00, gcrefRegs=00000000 {}, byrefRegs=00000000 {}, byref

IN0001: 000010 lea      rcx, bword ptr [V05 rsp+28H]
IN0002: 000015 mov      edx, 42
IN0003: 00001A call     IM`1[Int32][System.Int32]:DefaultM(int):System.Threading.Tasks.ValueTask
IN0004: 00001F nop

G_M27646_IG03:        ; offs=000020H, size=0005H, bbWeight=1    PerfScore 1.25, epilog, nogc, extend

IN0009: 000020 add      rsp, 56
IN000a: 000024 ret

EDIT: to have stats reported by jit-diff properly, it has to be patched to move coreclr binaries around, see dotnet/jitutils#296

@davidwrighton
Copy link
Member

@Rattenkrieg I would prefer some level of actual benchmarking, but the analysis you have provided is sufficient to convince me that there is value here, and this is worth implementing. I'm currently very focused on finishing up some details around crossgen2 compilation and that will occupy me for a few more days before I can deeply analyze the correctness of this change, and probably write a few extra test cases to cover possible problems I see. (Primarily around cases where the type implements multiple generic variants of the same interface.).

In addition, I'd like to see @AndyAyersMS or someone he designates take a look at the jit/jitinterface api side of this change. With this work, we are very close to supporting devirtualization of generic virtual methods, which would also be interesting, and it might be best to make the jit interface api reflect that possibility as well.

@AndyAyersMS
Copy link
Member

This is going to take a bit of time to sort through. But some quick initial thoughts:

  • superpmi handling for true IN, OUT parameters is going to be more complex than what you have implemented. From what I can tell we actually don't have any true IN, OUT parameters on the jit interface currently (resolveToken is marked that way but the set of fields read and written are distinct). If we keep with the current approach you will need to use the IN value for the key and record the OUT value as part of the result.
  • Because of that it might seem tempting to instead have separate IN and OUT params; at that point we might as well consider turning the entire set of arguments and results into a struct (much like we do for CORINFO_RESOLVED_TOKEN) with distinct in and out parts; then SPMI memoization can continue to work in the simple manner it does now.
  • Do you see any codegen impact in the framework code (pass -f to jit-diff)?

@Rattenkrieg
Copy link
Contributor Author

@AndyAyersMS I'll check params passing solution for CORINFO_RESOLVED_TOKEN and change my code accordingly, thanks!

jit-diff -f
> .\jitutils\bin\jit-diff diff --output c:\diffs --core_root <...>\Windows_NT.x64.Debug\Tests\Core_Root --diff <...>\patched\Windows_NT.x64.Debug\ --base <...>\artifacts\bin\coreclr\Windows_NT.x64.Debug\   --pmi -f

Beginning PMI CodeSize Diffs for System.Private.CoreLib.dll, framework assemblies
- Finished 268/268 Base 268/268 Diff [5699.9 sec]
Completed PMI CodeSize Diffs for System.Private.CoreLib.dll, framework assemblies in 5701.03s
Diffs (if any) can be viewed by comparing: c:\diffs\dasmset_50\base c:\diffs\dasmset_50\diff

git diff --no-index --diff-filter=M --exit-code --numstat c:\diffs\dasmset_50\diff c:\diffs\dasmset_50\base

Analyzing CodeSize diffs...
Found 9 files with textual diffs.

PMI CodeSize Diffs for System.Private.CoreLib.dll, framework assemblies for  default jit

Summary of Code Size diffs:
(Lower is better)

Total bytes of base: 54772077
Total bytes of diff: 54769555
Total bytes of delta: -2522 (-0.00% of base)
    diff is an improvement.

Top file improvements (bytes):
        -946 : Newtonsoft.Json.dasm (-0.12% of base)
        -588 : Microsoft.CodeAnalysis.VisualBasic.dasm (-0.01% of base)
        -578 : Microsoft.CodeAnalysis.CSharp.dasm (-0.01% of base)
        -197 : Microsoft.Diagnostics.Tracing.TraceEvent.dasm (-0.01% of base)
        -154 : System.ComponentModel.Composition.dasm (-0.05% of base)
         -28 : System.ComponentModel.Annotations.dasm (-0.07% of base)
         -20 : Microsoft.CodeAnalysis.dasm (-0.00% of base)
          -8 : System.Diagnostics.StackTrace.dasm (-0.19% of base)
          -3 : System.Linq.Expressions.dasm (-0.00% of base)

9 total files with Code Size differences (9 improved, 0 regressed), 259 unchanged.

Top method improvements (bytes):
        -500 (-1.68% of base) : Microsoft.CodeAnalysis.CSharp.dasm - Microsoft.CodeAnalysis.CSharp.CSharpCommandLineParser:Parse(System.Collections.Generic.IEnumerable`1[[System.String, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]],System.String,System.String,System.String):Microsoft.CodeAnalysis.CSharp.CSharpCommandLineArguments:this
        -490 (-1.42% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Microsoft.CodeAnalysis.VisualBasic.VisualBasicCommandLineParser:Parse(System.Collections.Generic.IEnumerable`1[[System.String, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]],System.String,System.String,System.String):Microsoft.CodeAnalysis.VisualBasic.VisualBasicCommandLineArguments:this
         -84 (-8.70% of base) : System.ComponentModel.Composition.dasm - System.ComponentModel.Composition.Hosting.CompositionServices:GetPartMetadataForType(System.Type,int):System.Collections.Generic.IDictionary`2[[System.String, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Object, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]
         -73 (-4.68% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.JsonValidatingReader:get_CurrentMemberSchemas():System.Collections.Generic.IList`1[[Newtonsoft.Json.Schema.JsonSchemaModel, Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed]]:this
         -60 (-9.19% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[__Canon][System.__Canon]:CallMethodReturnLast(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],Fallback[__Canon]):System.Dynamic.DynamicMetaObject:this
         -60 (-10.08% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Byte][System.Byte]:CallMethodReturnLast(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],Fallback[Byte]):System.Dynamic.DynamicMetaObject:this
         -60 (-10.08% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Int16][System.Int16]:CallMethodReturnLast(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],Fallback[Int16]):System.Dynamic.DynamicMetaObject:this
         -60 (-10.08% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Int32][System.Int32]:CallMethodReturnLast(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],Fallback[Int32]):System.Dynamic.DynamicMetaObject:this
         -60 (-10.08% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Double][System.Double]:CallMethodReturnLast(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],Fallback[Double]):System.Dynamic.DynamicMetaObject:this
         -60 (-10.08% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Vector`1][System.Numerics.Vector`1[System.Single]]:CallMethodReturnLast(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],Fallback[Vector`1]):System.Dynamic.DynamicMetaObject:this
         -60 (-10.08% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Int64][System.Int64]:CallMethodReturnLast(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],Fallback[Int64]):System.Dynamic.DynamicMetaObject:this
         -36 (-0.73% of base) : Microsoft.Diagnostics.Tracing.TraceEvent.dasm - Microsoft.Diagnostics.Tracing.Etlx.TraceLog:CopyRawEvents(Microsoft.Diagnostics.Tracing.TraceEventDispatcher,FastSerialization.IStreamWriter):this
         -30 (-3.71% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[__Canon][System.__Canon]:BuildCallMethodWithResult(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],System.Dynamic.DynamicMetaObject,Fallback[__Canon]):System.Dynamic.DynamicMetaObject:this
         -30 (-4.02% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Byte][System.Byte]:BuildCallMethodWithResult(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],System.Dynamic.DynamicMetaObject,Fallback[Byte]):System.Dynamic.DynamicMetaObject:this
         -30 (-4.02% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Int16][System.Int16]:BuildCallMethodWithResult(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],System.Dynamic.DynamicMetaObject,Fallback[Int16]):System.Dynamic.DynamicMetaObject:this
         -30 (-4.02% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Int32][System.Int32]:BuildCallMethodWithResult(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],System.Dynamic.DynamicMetaObject,Fallback[Int32]):System.Dynamic.DynamicMetaObject:this
         -30 (-4.02% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Double][System.Double]:BuildCallMethodWithResult(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],System.Dynamic.DynamicMetaObject,Fallback[Double]):System.Dynamic.DynamicMetaObject:this
         -30 (-4.02% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Vector`1][System.Numerics.Vector`1[System.Single]]:BuildCallMethodWithResult(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],System.Dynamic.DynamicMetaObject,Fallback[Vector`1]):System.Dynamic.DynamicMetaObject:this
         -30 (-4.02% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Int64][System.Int64]:BuildCallMethodWithResult(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],System.Dynamic.DynamicMetaObject,Fallback[Int64]):System.Dynamic.DynamicMetaObject:this
         -30 (-2.08% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.ExpressionReflectionDelegateFactory:BuildMethodCall(System.Reflection.MethodBase,System.Type,System.Linq.Expressions.ParameterExpression,System.Linq.Expressions.ParameterExpression):System.Linq.Expressions.Expression:this

Top method improvements (percentages):
         -60 (-10.08% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Byte][System.Byte]:CallMethodReturnLast(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],Fallback[Byte]):System.Dynamic.DynamicMetaObject:this
         -60 (-10.08% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Int16][System.Int16]:CallMethodReturnLast(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],Fallback[Int16]):System.Dynamic.DynamicMetaObject:this
         -60 (-10.08% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Int32][System.Int32]:CallMethodReturnLast(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],Fallback[Int32]):System.Dynamic.DynamicMetaObject:this
         -60 (-10.08% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Double][System.Double]:CallMethodReturnLast(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],Fallback[Double]):System.Dynamic.DynamicMetaObject:this
         -60 (-10.08% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Vector`1][System.Numerics.Vector`1[System.Single]]:CallMethodReturnLast(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],Fallback[Vector`1]):System.Dynamic.DynamicMetaObject:this
         -60 (-10.08% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Int64][System.Int64]:CallMethodReturnLast(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],Fallback[Int64]):System.Dynamic.DynamicMetaObject:this
         -60 (-9.19% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[__Canon][System.__Canon]:CallMethodReturnLast(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Collections.Generic.IEnumerable`1[[System.Linq.Expressions.Expression, System.Linq.Expressions, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a]],Fallback[__Canon]):System.Dynamic.DynamicMetaObject:this
         -84 (-8.70% of base) : System.ComponentModel.Composition.dasm - System.ComponentModel.Composition.Hosting.CompositionServices:GetPartMetadataForType(System.Type,int):System.Collections.Generic.IDictionary`2[[System.String, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Object, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]
         -30 (-8.29% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Schema.JsonSchemaBuilder:ProcessExtends(Newtonsoft.Json.Linq.JToken):this
         -26 (-5.74% of base) : Microsoft.Diagnostics.Tracing.TraceEvent.dasm - Microsoft.Diagnostics.Tracing.Etlx.TraceProcesses:ToString():System.String:this
         -26 (-5.74% of base) : Microsoft.Diagnostics.Tracing.TraceEvent.dasm - Microsoft.Diagnostics.Tracing.Etlx.TraceThreads:ToString():System.String:this
         -26 (-5.74% of base) : Microsoft.Diagnostics.Tracing.TraceEvent.dasm - Microsoft.Diagnostics.Tracing.Etlx.TraceModuleFiles:ToString():System.String:this
         -20 (-5.49% of base) : Microsoft.CodeAnalysis.VisualBasic.dasm - Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax.SyntaxNodeExtensions:CreateSkippedTrivia(Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax.VisualBasicSyntaxNode,bool,bool,Microsoft.CodeAnalysis.DiagnosticInfo):Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax.SyntaxList`1[[Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax.VisualBasicSyntaxNode, Microsoft.CodeAnalysis.VisualBasic, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]
         -10 (-5.41% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Converters.ExpandoObjectConverter:ReadList(Newtonsoft.Json.JsonReader):System.Object:this
         -22 (-4.94% of base) : Microsoft.Diagnostics.Tracing.TraceEvent.dasm - Microsoft.Diagnostics.Tracing.Analysis.TraceProcesses:ToString():System.String:this
         -20 (-4.78% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Byte][System.Byte]:CallMethodNoResult(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Linq.Expressions.Expression[],Fallback[Byte]):System.Dynamic.DynamicMetaObject:this
         -20 (-4.78% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Int16][System.Int16]:CallMethodNoResult(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Linq.Expressions.Expression[],Fallback[Int16]):System.Dynamic.DynamicMetaObject:this
         -20 (-4.78% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Int32][System.Int32]:CallMethodNoResult(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Linq.Expressions.Expression[],Fallback[Int32]):System.Dynamic.DynamicMetaObject:this
         -20 (-4.78% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Double][System.Double]:CallMethodNoResult(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Linq.Expressions.Expression[],Fallback[Double]):System.Dynamic.DynamicMetaObject:this
         -20 (-4.78% of base) : Newtonsoft.Json.dasm - Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Vector`1][System.Numerics.Vector`1[System.Single]]:CallMethodNoResult(System.String,System.Dynamic.DynamicMetaObjectBinder,System.Linq.Expressions.Expression[],Fallback[Vector`1]):System.Dynamic.DynamicMetaObject:this

64 total methods with Code Size differences (64 improved, 0 regressed), 340865 unchanged.
Completed analysis in 34.54s

All these improvements are due to #43668 (comment)
Newtonsoft.Json.JsonValidatingReader:get_CurrentMemberSchemas()
Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[__Canon][System.__Canon]:CallMethodReturnLast
Newtonsoft.Json.Utilities.DynamicProxyMetaObject`1[Byte][System.Byte]:CallMethodReturnLast same diff we got for Int32, Vector etc
Newtonsoft.Json.Utilities.ExpressionReflectionDelegateFactory:BuildMethodCall
Microsoft.CodeAnalysis.CSharp.CSharpCommandLineParser:Parse
System.ComponentModel.Composition.Hosting.CompositionServices:GetPartMetadataForType

@Rattenkrieg
Copy link
Contributor Author

@davidwrighton

I'm currently very focused on finishing up some details around crossgen2 compilation and that will occupy me for a few more days before I can deeply analyze the correctness of this change, and probably write a few extra test cases to cover possible problems I see. (Primarily around cases where the type implements multiple generic variants of the same interface.).

No rush needed! I've pushed some tests I used for debugging, most of them are primitive since I was primarily interested in straightforward asm for analysis. But there are few testing "cases where the type implements multiple generic variants of the same interface". I don't propose to merge those, just added it for reference. Now I'm going to work on benchmarks, mostly for methods were reported as changed by jit-diff.

@Rattenkrieg Rattenkrieg force-pushed the default-interface-method-virtualization branch from c751e71 to f145018 Compare November 3, 2020 13:18
@Rattenkrieg
Copy link
Contributor Author

Here are benchmarks made from pushed tests. Ran with

COMPlus_TieredCompilation=0 python ./scripts/benchmarks_ci.py --frameworks net6.0 \
--filter 'Devirtualization*' \                              
--corerun \
~/projs/runtime/artifacts/bin/testhost/net6.0-Linux-Release-x64/shared/Microsoft.NETCore.App/6.0.0/corerun \
~/projs/patched/artifacts/bin/testhost/net6.0-Linux-Release-x64/shared/Microsoft.NETCore.App/6.0.0/corerun 
results
BenchmarkDotNet=v0.12.1.1448-nightly, OS=fedora 33
Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET SDK=6.0.100-alpha.1.20553.9
  [Host]     : .NET 6.0.0 (6.0.20.55204), X64 RyuJIT
  Job-UOOGDK : .NET 6.0 (42.42.42.42424), X64 RyuJIT
  Job-ENJNBQ : .NET 6.0 (42.42.42.42424), X64 RyuJIT

PowerPlanMode=00000000-0000-0000-0000-000000000000  Arguments=/p:DebugType=portable  IterationTime=250.0000 ms  
MaxIterationCount=20  MinIterationCount=15  WarmupCount=1  
Method Job Toolchain Mean Error StdDev Median Min Max Ratio Gen 0 Gen 1 Gen 2 Allocated
DevirttestStructs Job-UOOGDK /patched/artifacts/bin/testhost/net6.0-Linux-Release-x64/shared/Microsoft.NETCore.App/6.0.0/corerun 33.96 ns 0.199 ns 0.186 ns 33.89 ns 33.74 ns 34.27 ns 0.80 0.0076 - - 48 B
DevirttestStructs Job-ENJNBQ /runtime/artifacts/bin/testhost/net6.0-Linux-Release-x64/shared/Microsoft.NETCore.App/6.0.0/corerun 42.51 ns 0.294 ns 0.260 ns 42.53 ns 41.85 ns 42.81 ns 1.00 0.0114 - - 72 B
Method Job Toolchain Mean Error StdDev Median Min Max Ratio Gen 0 Gen 1 Gen 2 Allocated
DevirttestSingleVariance Job-DTZMLL /patched/artifacts/bin/testhost/net6.0-Linux-Release-x64/shared/Microsoft.NETCore.App/6.0.0/corerun 16.00 ns 0.111 ns 0.099 ns 15.99 ns 15.80 ns 16.17 ns 0.90 0.0038 - - 24 B
DevirttestSingleVariance Job-ZGIIZQ /runtime/artifacts/bin/testhost/net6.0-Linux-Release-x64/shared/Microsoft.NETCore.App/6.0.0/corerun 17.75 ns 0.146 ns 0.137 ns 17.70 ns 17.59 ns 18.00 ns 1.00 0.0038 - - 24 B
Method Job Toolchain Mean Error StdDev Median Min Max Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
DevirttestSingleValueTypeNoDim Job-WIRPMH /patched/artifacts/bin/testhost/net6.0-Linux-Release-x64/shared/Microsoft.NETCore.App/6.0.0/corerun 33.72 ns 0.316 ns 0.264 ns 33.75 ns 33.22 ns 34.15 ns 0.98 0.02 0.0063 - - 40 B
DevirttestSingleValueTypeNoDim Job-THTLQV /runtime/artifacts/bin/testhost/net6.0-Linux-Release-x64/shared/Microsoft.NETCore.App/6.0.0/corerun 34.38 ns 0.711 ns 0.594 ns 34.46 ns 33.32 ns 35.48 ns 1.00 0.00 0.0101 - - 64 B
Method Job Toolchain Mean Error StdDev Median Min Max Ratio Gen 0 Gen 1 Gen 2 Allocated
DevirttestSingleOverStruct Job-MNZJNQ /patched/artifacts/bin/testhost/net6.0-Linux-Release-x64/shared/Microsoft.NETCore.App/6.0.0/corerun 11.73 ns 0.127 ns 0.119 ns 11.67 ns 11.58 ns 12.00 ns 0.69 - - - -
DevirttestSingleOverStruct Job-HBBMYE /runtime/artifacts/bin/testhost/net6.0-Linux-Release-x64/shared/Microsoft.NETCore.App/6.0.0/corerun 16.95 ns 0.173 ns 0.144 ns 16.91 ns 16.78 ns 17.31 ns 1.00 0.0038 - - 24 B
Method Job Toolchain Mean Error StdDev Median Min Max Ratio Gen 0 Gen 1 Gen 2 Allocated
DevirttestSingleOverridingGeneric Job-GJCFYK /patched/artifacts/bin/testhost/net6.0-Linux-Release-x64/shared/Microsoft.NETCore.App/6.0.0/corerun 32.94 ns 0.147 ns 0.123 ns 32.96 ns 32.65 ns 33.14 ns 1.02 0.0101 - - 64 B
DevirttestSingleOverridingGeneric Job-AFELWE /runtime/artifacts/bin/testhost/net6.0-Linux-Release-x64/shared/Microsoft.NETCore.App/6.0.0/corerun 32.42 ns 0.242 ns 0.227 ns 32.41 ns 32.00 ns 32.78 ns 1.00 0.0101 - - 64 B
Method Job Toolchain Mean Error StdDev Median Min Max Ratio Gen 0 Gen 1 Gen 2 Allocated
DevirttestSingleOverriding Job-ORLOJQ /patched/artifacts/bin/testhost/net6.0-Linux-Release-x64/shared/Microsoft.NETCore.App/6.0.0/corerun 1.264 ns 0.0214 ns 0.0200 ns 1.252 ns 1.245 ns 1.308 ns 0.18 - - - -
DevirttestSingleOverriding Job-UUMXEC /runtime/artifacts/bin/testhost/net6.0-Linux-Release-x64/shared/Microsoft.NETCore.App/6.0.0/corerun 7.021 ns 0.0767 ns 0.0718 ns 7.009 ns 6.928 ns 7.148 ns 1.00 0.0038 - - 24 B
Method Job Toolchain Mean Error StdDev Median Min Max Ratio Gen 0 Gen 1 Gen 2 Allocated
DevirttestInline Job-MDETMM /patched/artifacts/bin/testhost/net6.0-Linux-Release-x64/shared/Microsoft.NETCore.App/6.0.0/corerun 7.795 ns 0.0910 ns 0.0851 ns 7.795 ns 7.606 ns 7.934 ns 0.53 0.0051 - - 32 B
DevirttestInline Job-FIUGOW /runtime/artifacts/bin/testhost/net6.0-Linux-Release-x64/shared/Microsoft.NETCore.App/6.0.0/corerun 14.603 ns 0.0807 ns 0.0715 ns 14.608 ns 14.483 ns 14.714 ns 1.00 0.0089 - - 56 B
Method Job Toolchain Mean Error StdDev Median Min Max Ratio Gen 0 Gen 1 Gen 2 Allocated
Devirttest Job-UVTLEC /patched/artifacts/bin/testhost/net6.0-Linux-Release-x64/shared/Microsoft.NETCore.App/6.0.0/corerun 30.32 ns 0.176 ns 0.147 ns 30.31 ns 30.08 ns 30.65 ns 0.88 0.0037 - - 24 B
Devirttest Job-VZGJXJ /runtime/artifacts/bin/testhost/net6.0-Linux-Release-x64/shared/Microsoft.NETCore.App/6.0.0/corerun 34.31 ns 0.239 ns 0.212 ns 34.34 ns 34.03 ns 34.63 ns 1.00 0.0038 - - 24 B
Method Job Toolchain Mean Error StdDev Median Min Max Ratio Gen 0 Gen 1 Gen 2 Allocated
Devirttest39419 Job-DPIOZQ /patched/artifacts/bin/testhost/net6.0-Linux-Release-x64/shared/Microsoft.NETCore.App/6.0.0/corerun 0.0057 ns 0.0068 ns 0.0064 ns 0.0028 ns 0.0000 ns 0.0213 ns 0.001 - - - -
Devirttest39419 Job-SBGEWN /runtime/artifacts/bin/testhost/net6.0-Linux-Release-x64/shared/Microsoft.NETCore.App/6.0.0/corerun 5.4767 ns 0.0385 ns 0.0360 ns 5.4802 ns 5.4021 ns 5.5348 ns 1.000 0.0038 - - 24 B

@@ -1289,7 +1289,7 @@ void MethodContext::recTryResolveToken(CORINFO_RESOLVED_TOKEN* pResolvedToken, b

TryResolveTokenValue value;

value.tokenOut = SpmiRecordsHelper::StoreAgnostic_CORINFO_RESOLVED_TOKENout(pResolvedToken, ResolveToken);
value.tokenOut = SpmiRecordsHelper::StoreAgnostic_CORINFO_RESOLVED_TOKENout(pResolvedToken, TryResolveToken); // copypaste artifacts?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Be advised, although I'm not 100% sure, same below.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, it looks like the wrong map is getting used here. Suspect the keys are conformable and don't collide so we never noticed.

cc @sandreenko

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree, thanks for catching this.

@Rattenkrieg
Copy link
Contributor Author

I don't know the reason behind timeouts in few tests, have everything passing on my machine. Besides that I think that I'm done with implementation.

@Rattenkrieg
Copy link
Contributor Author

Rattenkrieg commented Nov 28, 2020

Format is failing on CI
clang-tidy not found here: https://clrjit.blob.core.windows.net/clang-tools/centos.7-x64/clang-tidy
Either clang-tidy or clang-format was not installed. Please install and put them on the PATH to use jit-format.
Tools can be found at http://llvm.org/releases/download.html#3.8.0
ERROR: Failed to download clang-format and clang-tidy.
('Downloading', 'https://raw.githubusercontent.com/dotnet/jitutils/master/bootstrap.sh', 'to', '/tmp/tmpWMFNjA/bootstrap.sh')
Making bootstrap executable
('Running:', 'bash', '/tmp/tmpWMFNjA/bootstrap.sh')
Deleting /tmp/tmpWMFNjA/bootstrap.sh
Bootstrap failed
Same on my machine
Installing version 3.8 of clang tools
clang-format not found here: https://clrjit.blob.core.windows.net/clang-tools/fedora.33-x64/clang-format
clang-tidy not found here: https://clrjit.blob.core.windows.net/clang-tools/fedora.33-x64/clang-tidy
Either clang-tidy or clang-format was not installed. Please install and put them on the PATH to use jit-format.
Tools can be found at http://llvm.org/releases/download.html#3.8.0
ERROR: Failed to download clang-format and clang-tidy.
Deleting /tmp/tmpsontbc74/bootstrap.sh
Bootstrap failed
Windows
PS C:\Users\sergey\source\repos\runtime> python .\src\tests\Common\scripts\format.py -c .\src\coreclr -o Windows -a x64
Downloading https://raw.githubusercontent.com/dotnet/jitutils/master/ to C:\Users\sergey\AppData\Local\Temp\tmpbhz2gbc2\
Traceback (most recent call last):
  File ".\src\tests\Common\scripts\format.py", line 248, in <module>
    return_code = main(sys.argv[1:])
  File ".\src\tests\Common\scripts\format.py", line 129, in main
    urlretrieve(bootstrapUrl, bootstrapPath)
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.8_3.8.1776.0_x64__qbz5n2kfra8p0\lib\urllib\request.py", line 247, in urlretrieve
    with contextlib.closing(urlopen(url, data)) as fp:
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.8_3.8.1776.0_x64__qbz5n2kfra8p0\lib\urllib\request.py", line 222, in urlopen
    return opener.open(url, data, timeout)
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.8_3.8.1776.0_x64__qbz5n2kfra8p0\lib\urllib\request.py", line 531, in open
    response = meth(req, response)
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.8_3.8.1776.0_x64__qbz5n2kfra8p0\lib\urllib\request.py", line 640, in http_response
    response = self.parent.error(
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.8_3.8.1776.0_x64__qbz5n2kfra8p0\lib\urllib\request.py", line 569, in error
    return self._call_chain(*args)
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.8_3.8.1776.0_x64__qbz5n2kfra8p0\lib\urllib\request.py", line 502, in _call_chain
    result = func(*args)
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.8_3.8.1776.0_x64__qbz5n2kfra8p0\lib\urllib\request.py", line 649, in http_error_default
    raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 400: Bad Request

@Rattenkrieg Rattenkrieg force-pushed the default-interface-method-virtualization branch from 5fe23e4 to e91c55b Compare December 12, 2020 20:12
@Rattenkrieg
Copy link
Contributor Author

@davidwrighton @AndyAyersMS PTAL
There is bunch of tests which probably have to be merged/cleaned up. Also I've marked some existing comments in code which left me in doubt, looking for your feedback there as well.

Initially I thought that my solution is incomplete wrt generic methods on non-generic interfaces but tests demonstrated that we never touch devirtualization for such cases. Consider:

interface I
{
    string DefaultTypeOf<T>() => typeof(T).Name;
}

class C : I
{
}

public static class Program
{
    [MethodImpl(MethodImplOptions.AggressiveOptimization)]
    static int Main()
    {
        I c = new C();
        var dcs = c.DefaultTypeOf();
        if (dcs != "Object") return 200;
        return 100;
    }
}

produces

IN0001: 000008 mov      rdi, 0x12293E518
IN0002: 000012 call     CORINFO_HELP_NEWSFAST
IN0003: 000017 mov      rbx, rax
IN0004: 00001A mov      rdi, rbx
IN0005: 00001D call     System.Object:.ctor():this
IN0006: 000022 mov      rdi, rbx
IN0007: 000025 mov      rsi, 0x12293E3C8
IN0008: 00002F mov      rdx, 0x12293E718
IN0009: 000039 call     CORINFO_HELP_VIRTUAL_FUNC_PTR
IN000a: 00003E mov      rdi, rbx
IN000b: 000041 call     rax
IN000c: 000043 mov      rdi, rax
IN000d: 000046 mov      rsi, 0x19B2371F0
IN000e: 000050 mov      rsi, gword ptr [rsi]
IN000f: 000053 call     System.String:op_Inequality(System.String,System.String):bool
IN0010: 000058 test     eax, eax
IN0011: 00005A je       SHORT G_M24375_IG05

If we remove DIM and let class implement interface asm stays the same. However if we remove interface, method inlines perfectly:

class C
{
    public string DefaultTypeOf<T>() => typeof(T).Name;
}
IN0001: 000004 mov      rdi, 0x11CFFE3F8
IN0002: 00000E call     CORINFO_HELP_NEWSFAST
IN0003: 000013 mov      rdi, rax
IN0004: 000016 call     System.Object:.ctor():this
IN0005: 00001B mov      rdi, 0x11CDA4388
IN0006: 000025 call     CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE
IN0007: 00002A mov      rdi, rax
IN0008: 00002D mov      rax, 0x11CC5A5F0
IN0009: 000037 call     gword ptr [rax]System.RuntimeType:get_Name():System.String:this
IN000a: 000039 mov      rdi, rax
IN000b: 00003C mov      rsi, 0x19D8FD1F0
IN000c: 000046 mov      rsi, gword ptr [rsi]
IN000d: 000049 call     System.String:op_Inequality(System.String,System.String):bool
IN000e: 00004E test     eax, eax
IN000f: 000050 je       SHORT G_M24375_IG05

But only for one generic param:

class C
{
    public string DefaultTypeOf<T1, T2>() => typeof(T1).Name + typeof(T2).Name;
}
IN0001: 000008 mov      rdi, 0x10C68E3F8
IN0002: 000012 call     CORINFO_HELP_NEWSFAST
IN0003: 000017 mov      rbx, rax
IN0004: 00001A mov      rdi, rbx
IN0005: 00001D call     System.Object:.ctor():this
IN0006: 000022 mov      rdi, rbx
IN0007: 000025 mov      rsi, 0x10C68E608
IN0008: 00002F call     C:DefaultTypeOf():System.String:this
IN0009: 000034 mov      rdi, rax
IN000a: 000037 mov      rsi, 0x194F911F0
IN000b: 000041 mov      rsi, gword ptr [rsi]
IN000c: 000044 call     System.String:op_Inequality(System.String,System.String):bool
IN000d: 000049 test     eax, eax
IN000e: 00004B je       SHORT G_M24375_IG05

Have we ever considered optimizing such case?

@AndyAyersMS
Copy link
Member

Initially I thought that my solution is incomplete wrt generic methods on non-generic interfaces but tests demonstrated that we never touch devirtualization for such cases.

I believe all generic virtual methods are handled via the ldvirtftn / calli path and that is not yet hooked into devirtualization.

I had been thinking it wouldn't be worth addressing until after the jit is able propagate methods into callis and turn those into direct (and inlineable) calls, but the cost of the lookup is high enough that perhaps it's worth optimizing just that part.

@Rattenkrieg
Copy link
Contributor Author

Hey @davidwrighton is there still an interest from dotnet team/you in this PR?

@karelz karelz removed the request for review from a team February 2, 2021 14:49
public static class Program
{
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
static int Main()
Copy link
Member

Choose a reason for hiding this comment

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

We should find a way to test this without AggressiveOptimization. Currently that will disable the Crossgen compiler and so we won't test the behavior in ahead of time compile scenarios.

@@ -0,0 +1,31 @@
using System;
Copy link
Member

Choose a reason for hiding this comment

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

u [](start = 0, length = 1)

All of the test files need the copyright attribution comment.

@@ -2094,6 +2094,7 @@ class MethodTable

MethodDesc *GetMethodDescForInterfaceMethod(TypeHandle ownerType, MethodDesc *pInterfaceMD, BOOL throwOnConflict);
MethodDesc *GetMethodDescForInterfaceMethod(MethodDesc *pInterfaceMD, BOOL throwOnConflict); // You can only use this one for non-generic interfaces
// ^-- I don't believe this is correct statement, we have PRECONDITION(!pInterfaceMD->HasClassOrMethodInstantiation()); which implies it can be used with generic interfaces
Copy link
Member

Choose a reason for hiding this comment

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

That is not the meaning of the precondition. Its actually the opposite. If the precondition isn't true, the method can't be used, and if the method is on a generic interface then HasClassOrMethodInstantiation will return true, so !HasClassOrMethodInstantiation will be false.

@@ -581,7 +581,7 @@ class MethodDesc
//
// RequiresInstMethodDescArg()
// The method is itself generic and is shared between generic
// instantiations but is not itself generic. Furthermore
// instantiations but is not itself generic. WTF LOL. Furthermore
Copy link
Member

Choose a reason for hiding this comment

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

Words are hard, and copy/pasting is easy. The "but is not itself generic" is just wrong and should be deleted from this comment.

@@ -581,7 +581,7 @@ class MethodDesc
//
// RequiresInstMethodDescArg()
// The method is itself generic and is shared between generic
// instantiations but is not itself generic. Furthermore
// instantiations but is not itself generic. WTF LOL. Furthermore
// no "this" pointer is given (e.g. a value type method), so we pass in the
// exact-instantiation method table as an extra argument.
Copy link
Member

Choose a reason for hiding this comment

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

method table [](start = 27, length = 12)

This should read "MethodDesc"

MethodTable* pOwnerMT = OwnerClsHnd.GetMethodTable();
pOwnerMT = OwnerClsHnd.GetMethodTable();

if (!canCastStraightForward && !(pOwnerMT->IsInterface() && pObjMT->CanCastToInterface(pOwnerMT)))
Copy link
Member

Choose a reason for hiding this comment

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

What scenario are you addressing here?

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I found your explanation above. If pOwnerMT isn't an interface, why don't we return false here?


In reply to: 570649611 [](ancestors = 570649611)

@@ -7762,7 +7762,7 @@ getMethodInfoHelper(
ftn,
false);

// Shared generic or static per-inst methods and shared methods on generic structs
// Shared generic or static per-inst methods, shared methods on generic structs and default interface methods
Copy link
Member

Choose a reason for hiding this comment

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

This is a good comment change.

@davidwrighton
Copy link
Member

@Rattenkrieg We are interested in this change, and based on the performance numbers I'm convinced its a nice small worthwhile change. One reason why this feature wasn't built is that it crosses feature areas and expertise of various teams, and is difficult for us to actually review for correctness.

I have a few concerns

  1. Testing. This sort of thing is always difficult to get right. I think your tests are pretty good now, we you should have a couple tests showing calls from shared generic code itself.
  2. I am not qualified to review the JIT changes. You'll need to get @AndyAyersMS or someone he delegates to review them.
  3. Crossgen2 changes. I find those to be of particular interest. As Crossgen2 doesn't currently actually handle default interface methods, I want to know how you found the problems here, and if they are problems without your JIT changes.

@danmoseley
Copy link
Member

Assigning a shepherd for this PR, feel free to change.

Base automatically changed from master to main March 1, 2021 09:07
@davidwrighton
Copy link
Member

@Rattenkrieg Are you able to answer my questions? If so, please let us know, as otherwise we'll be closing this PR soon.

@Rattenkrieg
Copy link
Contributor Author

Rattenkrieg commented Mar 23, 2021 via email

@lambdageek
Copy link
Member

lambdageek commented Mar 23, 2021

@Rattenkrieg you can append ?timeline_per_page=10 to the page URL to get it to open in a browser. https://github.com/dotnet/runtime/pull/43668?timeline_per_page=10 #43668

@Rattenkrieg
Copy link
Contributor Author

Thank you @lambdageek now I'm back online. @davidwrighton I will address your feedback (hopefully) this weekend and will open a new PR with smaller carbon footprint.

@mangod9
Copy link
Member

mangod9 commented Apr 26, 2021

@Rattenkrieg can this PR be closed since you are hoping to open a new one after addressing feedback?

@Rattenkrieg
Copy link
Contributor Author

@mangod9 sorry for inconvenience. I was planning to resubmit changes like month ago but currently I have technical issues. I'm really really looking to open new PR soon.

@mangod9
Copy link
Member

mangod9 commented Apr 27, 2021

Thanks for the update!

@karelz karelz added this to the 6.0.0 milestone May 20, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Jun 19, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Default interfaces] Support for default interface method devirtualization