-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Add JitEnregStats
complus.
#58776
Conversation
Tagging subscribers to this area: @JulieLeeMSFT Issue Detailsnull
|
/azp list |
Commenter does not have sufficient privileges for PR 58776 in repo dotnet/runtime |
/azp list |
@sandreenko what pipleline do you want to run? I will run it. |
thank you Julie, I was just testing how /azp will react. |
Hmm, I can't call @dotnet/jit-contrib even if I am inside it looks like. |
@eiriktsarpalis 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 |
There was a problem hiding this 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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;
src/coreclr/jit/lclvars.cpp
Outdated
LclVarDsc* varDsc = &lvaTable[varNum]; | ||
if (varDsc->lvDoNotEnregister == 1) | ||
{ | ||
return; |
There was a problem hiding this comment.
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)
?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
return; | ||
} | ||
|
||
if (m_addrExposed != 0) |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, done.
There was a problem hiding this 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
src/coreclr/jit/compiler.h
Outdated
@@ -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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
src/coreclr/jit/lclvars.cpp
Outdated
if (varDsc->lvDoNotEnregister == 1) | ||
{ | ||
return; | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
@sandreenko - Do you mind rebasing your branch and fixing formatting issues and then it should be ready to go. |
ea5e954
to
cfa2d3a
Compare
done, also sorted switches and prints according to the enum order. |
Since |
I can change that if you think it will be better. I think sometimes the presence of |
Thank you @sandreenko ! |
This PR adds a
COMPlus/DOTNET_JitEnregStats
to show additional information about our local enregistration.Example output for aspnet.run.windows.x64.checked.mch:
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
runtime/src/coreclr/jit/gentree.cpp
Lines 7808 to 7810 in 3fd30b0