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

PGO: GetLikelyClass changes missing logic to enable guarded devirtualization #51643

Closed
AndyAyersMS opened this issue Apr 21, 2021 · 8 comments
Closed
Assignees
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
Milestone

Comments

@AndyAyersMS
Copy link
Member

The jit won't attempt guarded devirtualization unless it sees class profile data in the PGO. That analysis needs to be updated to recognize the GetLikelyClass record types.

So currently the jit is ignoring all this class info. That may explain some of the regressions we saw in microbenchmarks.

Follow-on work from #51284.

@dotnet-issue-labeler dotnet-issue-labeler bot added area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI untriaged New issue has not been triaged by the area owner labels Apr 21, 2021
@AndyAyersMS AndyAyersMS self-assigned this Apr 21, 2021
@AndyAyersMS AndyAyersMS added this to the 6.0.0 milestone Apr 21, 2021
@AndyAyersMS AndyAyersMS removed the untriaged New issue has not been triaged by the area owner label Apr 21, 2021
@AndyAyersMS
Copy link
Member Author

There may be other things here to sort out too. If I dump the pgo data from a Release SPC, I see:

ref !1 System.Collections.Generic.Dictionary`2<__Canon,__Canon>.FindValue(!0)
Handle: 0x060064DC
Rid: 25820
EntryPointRuntimeFunctionId: 25490
Number of RuntimeFunctions: 1
Number of fixups: 2
    TableIndex 4, Offset 022B: System.RuntimeType (TYPE_HANDLE)
    TableIndex 4, Offset 02B6: System.ExceptionArgument (TYPE_HANDLE)

PGO info:
ILOffset: 0 InstrumentationKind: NumRuns Other: 14 Count: 1
ILOffset: 8 InstrumentationKind: EdgeIntCount Other: 14 Count: 1
0
ILOffset: 52 InstrumentationKind: GetLikelyClass Other: 1833 Count: 1
System.RuntimeType (TYPE_HANDLE)
ILOffset: 102 InstrumentationKind: EdgeIntCount Other: 383 Count: 1
0
ILOffset: 132 InstrumentationKind: EdgeIntCount Other: 154 Count: 1
0
ILOffset: 154 InstrumentationKind: EdgeIntCount Other: 102 Count: 1
0
ILOffset: 194 InstrumentationKind: EdgeIntCount Other: 383 Count: 1
48315
ILOffset: 224 InstrumentationKind: EdgeIntCount Other: 243 Count: 1
116047
ILOffset: 233 InstrumentationKind: GetLikelyClass Other: 2333 Count: 1
System.Collections.Generic.ObjectEqualityComparer`1<System.Type> (TYPE_HANDLE)
ILOffset: 243 InstrumentationKind: EdgeIntCount Other: 194 Count: 1
62003
ILOffset: 265 InstrumentationKind: EdgeIntCount Other: 369 Count: 1
0
ILOffset: 304 InstrumentationKind: EdgeIntCount Other: 383 Count: 1
689639
ILOffset: 332 InstrumentationKind: EdgeIntCount Other: 347 Count: 1
184589
ILOffset: 347 InstrumentationKind: EdgeIntCount Other: 369 Count: 1
507837
ILOffset: 374 InstrumentationKind: EdgeIntCount Other: 381 Count: 1
300636
ILOffset: 381 InstrumentationKind: EdgeIntCount Other: 0 Count: 1
1069945

Which should say that System.RuntimeType has likelihood 41.
But when the jit gets to this point (IL offset 52) and calls getLikelyClass, it doesn't get this result, it gets back null with likelihood 1.

The likelihood problem comes from this bit of code

            *pLikelihood      = (UINT32)(schema[i].Other && 0xFF);

where we should use &, not &&.

and so we don't do guarded devirt:

Importing BB05 (PC=044) of 'Dictionary`2:FindValue(__Canon):byref:this'
    [ 0]  44 (0x02c) ldarga.s 1
    [ 1]  46 (0x02e) constrained. (1B000017) callvirt 060004D5
In Compiler::impImportCall: opcode is callvirt, kind=4, callRetType is int, structSize is 0

impDevirtualizeCall: Trying to devirtualize virtual call:
    class for 'this' is __Canon (attrib 20020000)
    base method is Object::GetHashCode
--- no derived method
    Class not final or exact, and method not final
Considering guarded devirtualization
No likely class, sorry

If we run forward to the second call site at offset 233 the returned class handle is f233 and this causes the runtime to crash if dumping is enabled, when the jit asks for the name of the type. So seems like this might be a type code or otherwise un-fixed-up type handle?

Simple-ish repro (using release runtime, checked jit)

using System;
using System.Collections.Generic;
using System.Threading;

class X
{
    public static void Main()
    {
        Dictionary<string, string> s = new Dictionary<string, string>();
        s["hello"] = "world";
        for (int i = 0; i < 100; i++)
        {
             _ = s["hello"];
             Thread.Sleep(15);
        }

        Thread.Sleep(100);

         for (int i = 0; i < 100_000; i++)
        {
             _ = s["hello"];
        }
    }    
}

@AndyAyersMS
Copy link
Member Author

cc @davidwrighton

Partial fixes on my branch here: main...AndyAyersMS:UpdateJitForNewLikelyClassRecords

@davidwrighton
Copy link
Member

Sigh. I'll pull that down and start taking a look.

@davidwrighton
Copy link
Member

@AndyAyersMS I've found the fix for that problem, but there is an additional issue I hit with an overeager assert that I'd like you to deal with. See AndyAyersMS#1 for details.

@AndyAyersMS
Copy link
Member Author

The jit also can't recreate the exact edge instrumentation schema... not clear what this means. Am going to see if the resolved flow seems plausible. I recall thinking we could suppress schema elements at times, but I don't recall implementing that.

Schema is missing non-tree edge BB09 -> BB24, will presume zero
 ... unknown edge BB12 -> BB13
 ... unknown edge BB13 -> BB14
 ... unknown edge BB14 -> BB15
 ... unknown edge BB14 -> BB16
 ... unknown edge BB16 -> BB17
Schema is missing non-tree edge BB15 -> BB24, will presume zero
 ... unknown edge BB18 -> BB19
 ... unknown edge BB19 -> BB20
 ... unknown edge BB20 -> BB21
 ... unknown edge BB20 -> BB22
Schema is missing non-tree edge BB22 -> BB19, will presume zero
Schema is missing non-tree edge BB21 -> BB24, will presume zero

The class profile info seems to line up ok, that is, we have class profiles at offsets 0x34 and xe9 and there are callvirts at those offsets (some of the other callvirts do not have class profiles, but it looks like they're in blocks that were never hit).

@AndyAyersMS
Copy link
Member Author

Thanks David. I'll take a look at that assert.

@AndyAyersMS
Copy link
Member Author

Looking better now:

impDevirtualizeCall: Trying to devirtualize virtual call:
    class for 'this' is __Canon (attrib 20020000)
    base method is Object::GetHashCode
--- no derived method
    Class not final or exact, and method not final
Considering guarded devirtualization at IL offset 52 (0x34)
Likely class for 00007FFE1C3D52B8 (__Canon) is 00007FFE1C3D8E30 (RuntimeType) [likelihood:41 classes seen:7]
virtual call would invoke method GetHashCode
Marking call [000234] as guarded devirtualization candidate; will guess for class RuntimeType

@AndyAyersMS
Copy link
Member Author

Fixed via #51664

@ghost ghost locked as resolved and limited conversation to collaborators May 23, 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

No branches or pull requests

2 participants