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

Add JitEnregStats complus. #58776

Merged
merged 7 commits into from
Sep 13, 2021

Conversation

sandreenko
Copy link
Contributor

@sandreenko sandreenko commented Sep 7, 2021

This PR adds a COMPlus/DOTNET_JitEnregStats to show additional information about our local enregistration.

Example output for aspnet.run.windows.x64.checked.mch:

Locals enregistration statistics:
total number of locals: 574832, number of enregistered: 345129, notEnreg: 229703, ratio: 0.60
total number of struct locals: 30650, number of enregistered: 369, notEnreg: 30281, ratio: 0.01
total number of primitive locals: 544182, number of enregistered: 344760, notEnreg: 199422, ratio: 0.63
m_addrExposed 34775, ratio: 0.15
m_notRegSizeStruct 639, ratio: 0.00
m_structArg 31, ratio: 0.00
m_blockOp 552, ratio: 0.00
m_localField 4798, ratio: 0.02
lvLiveInOutOfHndlr 1099, ratio: 0.00
lvDepField 1151, ratio: 0.01
lvNoRegVars 186363, ratio: 0.81
m_castTakedAddr 12, ratio: 0.00
m_swizzleArg 199, ratio: 0.00
m_blockOpRet 84, ratio: 0.00

Addr exposed details:
m_parentExposed 7910, ratio: 0.23
m_tooConservative 9008, ratio: 0.26
m_escapeAddress 17256, ratio: 0.50
m_copyFldByFld 593, ratio: 0.02
m_wideIndir 8, ratio: 0.00

I was surprised to see so many Tier0 (Min opts) methods in the collection but otherwise, it looks right (lvNoRegVars 186363, ratio: 0.81).

No diffs, but so some asserts in both base/diff spmi collection with GT_PHI

default:
NO_WAY("Cloning of node not supported");
goto DONE;
, has somebody else seen this and do we have an issue for it?

@ghost ghost added the community-contribution Indicates that the PR has been added by a community member label Sep 7, 2021
@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 and removed community-contribution Indicates that the PR has been added by a community member labels Sep 7, 2021
@ghost
Copy link

ghost commented Sep 7, 2021

Tagging subscribers to this area: @JulieLeeMSFT
See info in area-owners.md if you want to be subscribed.

Issue Details

null

Author: sandreenko
Assignees: -
Labels:

area-CodeGen-coreclr

Milestone: -

@sandreenko
Copy link
Contributor Author

/azp list

@azure-pipelines
Copy link

Commenter does not have sufficient privileges for PR 58776 in repo dotnet/runtime

@JulieLeeMSFT
Copy link
Member

/azp list

@JulieLeeMSFT
Copy link
Member

@sandreenko what pipleline do you want to run? I will run it.

@sandreenko sandreenko marked this pull request as ready for review September 8, 2021 03:03
@sandreenko
Copy link
Contributor Author

@sandreenko what pipleline do you want to run? I will run it.

thank you Julie, I was just testing how /azp will react.
Btw is there an option to create a draft PR that does not do this "Tagging subscribers to this area" (like a very raw change that the author wants to keep unnoticed for some time).

@sandreenko
Copy link
Contributor Author

Hmm, I can't call @dotnet/jit-contrib even if I am inside it looks like.

PTAL @BruceForstall @kunalspathak @AndyAyersMS

@JulieLeeMSFT
Copy link
Member

@sandreenko what pipleline do you want to run? I will run it.

thank you Julie, I was just testing how /azp will react.
Btw is there an option to create a draft PR that does not do this "Tagging subscribers to this area" (like a very raw change that the author wants to keep unnoticed for some time).

@eiriktsarpalis do you have access to the bot to not tag owners for draft PRs?

@eiriktsarpalis
Copy link
Member

eiriktsarpalis commented Sep 8, 2021

do you have access to the bot to not tag owners for draft PRs?

There doesn't seem to be an option to exclude draft PRs in FabricBot. We could perhaps ask the FB team to implement this /cc @danmoseley

Copy link
Member

@kunalspathak kunalspathak left a comment

Choose a reason for hiding this comment

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

Thanks for doing this. Added few comments.

@@ -382,6 +382,10 @@ CONFIG_INTEGER(JitMinOptsTrackGCrefs, W("JitMinOptsTrackGCrefs"), JitMinOptsTrac
// TODO-Cleanup: need to make 'MEASURE_MEM_ALLOC' well-defined here at all times.
CONFIG_INTEGER(DisplayMemStats, W("JitMemStats"), 0) // Display JIT memory usage statistics

#if defined(DEBUG)
CONFIG_INTEGER(JitEnregStats, W("JitEnregStats"), 0) // Display JIT enregistration statistics
Copy link
Member

Choose a reason for hiding this comment

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

nit: Have this under #ifdef DEBUG near LSRA stats for easy discoverability.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

will do.

shadowVarDsc->lvDoNotEnregister = varDsc->lvDoNotEnregister;
#ifdef DEBUG
shadowVarDsc->lvVMNeedsStackAddr = varDsc->lvVMNeedsStackAddr;
Copy link
Member

Choose a reason for hiding this comment

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

why we don't need to set this anymore?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These were the debug fields to explain why we don't enregister, now they are replaced with

    DoNotEnregisterReason m_doNotEnregReason;

    AddressExposedReason m_addrExposedReason;

LclVarDsc* varDsc = &lvaTable[varNum];
if (varDsc->lvDoNotEnregister == 1)
{
return;
Copy link
Member

Choose a reason for hiding this comment

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

Is it reasonable to add assert(varDsc->m_doNotEnregReason == reason)?

Copy link
Member

Choose a reason for hiding this comment

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

Or if varDsc was set to DoNotRegister by some other phase, then also set the reason here?

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 was thinking to do DoNotEnregisterReason enum to be a set like some other enums:

#define MAKE_DNRREASON(bit) (1ULL << (bit))
AddrExposed= = MAKE_DNRREASON( 0),
DontEnregStructs= = MAKE_DNRREASON( 1),

but because one reason is often causing others for the purpose of this dump it is clearer to keep only the first reason and not all of them for each var.

So we record only the first reason why something can't be put into a register and if different reasons come we ignore it.

newVarDsc->lvDoNotEnregister = varDsc->lvDoNotEnregister;
newVarDsc->lvLiveInOutOfHndlr = varDsc->lvLiveInOutOfHndlr;
newVarDsc->lvSingleDef = varDsc->lvSingleDef;
newVarDsc->lvSingleDefRegCandidate = varDsc->lvSingleDefRegCandidate;
newVarDsc->lvSpillAtSingleDef = varDsc->lvSpillAtSingleDef;
#ifdef DEBUG
newVarDsc->lvLclBlockOpAddr = varDsc->lvLclBlockOpAddr;
Copy link
Member

Choose a reason for hiding this comment

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

Why we don't need to clone these fields?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

answered above, they are deleted.

lvaTable[info.compThisArg].lvHasILStoreOp = false;
noway_assert(arg0varDsc->lvVerTypeInfo.IsThisPtr());
thisVarDsc->lvVerTypeInfo.ClearThisPtr();
// Note that here we don't clear `m_doNotEnregReason` and it stays
Copy link
Member

Choose a reason for hiding this comment

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

Why don't we clear address exposed reason?

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 saw only 1 method that was using this logic in benchmark collection so I did not dig deep here.

In fgAdjustForAddressExposedOrWrittenThis we are creating a clone for this and replace other uses of the original this with the clone, so we assume that now the reason why this was marked as address exposed is no longer valid and there could be no other reasons why it could be address exposed.
So after this transformation, the clone is address exposed and the original - not.

However, it is questionable if we can clear lvDoNotEnregister here as well because we don't have information if AddrExposed was the only reason why we can't enregister. So we can imagine a situation where we clear this flag and enregister the original this when we should not, for example, because local enreg is disabled or something like that.

src/coreclr/jit/compiler.cpp Outdated Show resolved Hide resolved
src/coreclr/jit/compiler.h Outdated Show resolved Hide resolved
return;
}

if (m_addrExposed != 0)
Copy link
Member

Choose a reason for hiding this comment

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

It will be better readable if we have a macro something like this:

#define PRINT_STATS(stat, total) \
    if (stat != 0) \
    { \
        fprintf(fout, #stat "%d, ratio: %.2f\n", stat, (float)stat / total); \
    } \

PRINT_STATS(m_addrExposed, notEnreg)
PRINT_STATS(m_notRegSizeStruct , notEnreg)
...
PRINT_STATS(m_copyFldByFld, m_addrExposed)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, done.

Copy link
Member

@BruceForstall BruceForstall left a comment

Choose a reason for hiding this comment

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

Looks like a nice cleanup, with useful stats

@@ -676,6 +707,49 @@ class LclVarDsc
return lvIsMultiRegArg || lvIsMultiRegRet;
}

private:
bool m_addrExposed : 1; // The address of this variable is "exposed" -- passed as an argument, stored in a
Copy link
Member

Choose a reason for hiding this comment

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

This isn't going to bitwise pack with anything, since there aren't adjacent bool bit ops. Is there a reason this can't stay as before? (unsigned char :1) I'm not sure if you can change public/private visibility within a packing set; something to check.

Also, which m_ is a "new standard", it seems like since nothing else in this class is defined with that naming convention, we should stick with what's already here and use the lv previx like everything else.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also, which m_ is a "new standard", it seems like since nothing else in this class is defined with that naming convention, we should stick with what's already here and use the lv previx like everything else.

the other fields in this struct are mostly public so for public lv prefix makes sense (if somebody is using grep to find them and not IDE tools) but for private fields I don't see any value in having them.

This isn't going to bitwise pack with anything, since there aren't adjacent bool bit ops. Is there a reason this can't stay as before? (unsigned char :1)

fixed, but with bool instead of char if you don't mind.

Copy link
Member

Choose a reason for hiding this comment

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

fixed, but with bool instead of char if you don't mind.

I thought that unsigned char :1 doesn't pack with bool :1 due to the rules of C bitfield packing, or maybe MSVC++ behavior. Can you check? It might be safest to use unsigned char just to ensure the greatest likelihood of packing across different compilers.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

https://godbolt.org/ shows that both clang and MSVC don't have problems with usnigned int a:1; bool b:1 packing.

@@ -440,6 +440,7 @@ class GlobalJitOptions
#define MEASURE_MEM_ALLOC 1 // Collect memory allocation stats.
#define LOOP_HOIST_STATS 1 // Collect loop hoisting stats.
#define TRACK_LSRA_STATS 1 // Collect LSRA stats
#define TRACK_ENREG_STATS 1 // Collect enregistration stats
Copy link
Member

Choose a reason for hiding this comment

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

Set

#define TRACK_ENREG_STATS 0

in the #else clause, like the other cases?

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 was considering it but the contract here is that you then will be able to set #define TRACK_ENREG_STATS 1 and get data in the release. It is not the case here, in order to make TRACK_ENREG_STATS 1 work in the release I would need to replace many DEBUGARG with something like

#if defined(DEBUG) || (TRACK_ENREG_STATS  == 1)
#define DEBUG_OR_TACK_ENREG_STATS(arg) , arg
#endif

and it looked to me as overkill, especially because for this mode we don't expect different results from the release and checked like for others.

if (varDsc->lvDoNotEnregister == 1)
{
return;
}
Copy link
Member

Choose a reason for hiding this comment

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

How about adding something to JitDump if the new reason is not the same as the existing reason?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

not sure about it, for example, lets say a var is marked as DoNotEnreg 2 times because of addrExposed and 2 times because of lclFld, the first reason is addrExposed, what would you prefer to see it the dump:
1.

should not be enregistered because: addrExposed
should not be enregistered also because: lclFld// a new reason
should not be enregistered because: addrExposed // the first reason called again
should not be enregistered also because: lclFld// a new reason, don't have information that we already had lclFld before.
should not be enregistered because: addrExposed
should not be enregistered also because: lclFld
should not be enregistered also because: addrExposed
should not be enregistered also because: lclFld
should not be enregistered because: addrExposed
should not be enregistered also because: lclFld
should not be enregistered also because: lclFld

Copy link
Member

Choose a reason for hiding this comment

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

I was thinking of (3). I think it's better for the lclFld lines (in this example) to be duplicated than for there to be no output at all. Unless you tell me the number of duplicated lines will be large (>20), at which point maybe there's some other issue (like, why do we call do-not-enreg so much?)

const GenTreeLclVarCommon* lclAddr = node->AsLclVarCommon();
comp->lvaSetVarDoNotEnregister(lclAddr->GetLclNum() DEBUGARG(Compiler::DNER_BlockOp));
const LclVarDsc* varDsc = comp->lvaGetDesc(lclAddr);
if (!varDsc->lvDoNotEnregister)
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this always call lvaSetVarDoNotEnregister and it can early-return if already set (but also possibly JitDump if already set for a different reason)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

not in this case in my opinion, if everything goes well it would be replaced with assert(varDsc->lvDoNotEnregister) soon because this code is a patch for some locals that are slipping morph analysis nowadays.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

not in this case in my opinion, if everything goes well it would be replaced with assert(varDsc->lvDoNotEnregister) soon because this code is a patch for some locals that are slipping morph analysis nowadays.

src/coreclr/jit/compiler.h Outdated Show resolved Hide resolved
src/coreclr/jit/compiler.cpp Outdated Show resolved Hide resolved
@kunalspathak
Copy link
Member

@sandreenko - Do you mind rebasing your branch and fixing formatting issues and then it should be ready to go.

@sandreenko sandreenko force-pushed the setDoNotEnregReasonForPR branch from ea5e954 to cfa2d3a Compare September 9, 2021 17:58
@sandreenko
Copy link
Contributor Author

@sandreenko - Do you mind rebasing your branch and fixing formatting issues and then it should be ready to go.

done, also sorted switches and prints according to the enum order.

@AndyAyersMS
Copy link
Member

Since lvNoRegVars is not a particularly interesting reason (at least in conjunction with Tier0), perhaps you should just do the normalized breakdown of cases where this doesn't appear...?

@sandreenko
Copy link
Contributor Author

Since lvNoRegVars is not a particularly interesting reason (at least in conjunction with Tier0), perhaps you should just do the normalized breakdown of cases where this doesn't appear...?

I can change that if you think it will be better. I think sometimes the presence of lvNoRegVars can indicate that we fall back to minOpts where we did not expect it, for example, when we do a replay of crossgen2 collection we don't expect any of them.

@kunalspathak
Copy link
Member

Thank you @sandreenko !

@kunalspathak kunalspathak merged commit b2fbf3d into dotnet:main Sep 13, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Nov 3, 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
None yet
Development

Successfully merging this pull request may close these issues.

6 participants