-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Fix bug in CheckIfTypeIsEquivalent helper #1368
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
Conversation
@pleath, Why aren't we checking all non-null cached type in the slow path? Is it better off to bailout ? Refers to: lib/Runtime/Language/JavascriptOperators.cpp:8247 in 7ce8997. [](commit_id = 7ce8997, deletion_comment = False) |
IIRC the type in slot 0 is the one that's checked by the online jitted code. If that's true it should always hold the last type we saw, and we should never allow it to become null. Is that not the case? -- Paul From: Satheesh Kumar Ravindranathmailto:[email protected]
@pleathhttps://github.com/pleath, Why aren't we checking all non-null cached type in the slow path? Is it better off to bailout ? Refers to: lib/Runtime/Language/JavascriptOperators.cpp:8247 in 7ce89977ce8997. <commit_id%20=%207ce8997c00764694c085e9dfefdbbedcdc894f18,%20deletion_comment%20=%20False> — |
AFAIK, cached types would have same prototype/typeId (and thats why there are equivalent types). I can add an assert to ensure this is the case. In reply to: 236767820 [](ancestors = 236767820) Refers to: lib/Runtime/Language/JavascriptOperators.cpp:8247 in 7ce8997. [](commit_id = 7ce8997, deletion_comment = False) |
I think we should look into why the caches are being zeroed. @kunalspathak , you mentioned the caches can get zeroed if the types weren't marked - that's weird because we pin all the types in an equivalent set at work item creation time. Right now the equivalent type cache can hold a maximum of 8 types, but we have been talking about making it growable. It would be nice to not have to loop through the cache for the first non-null entry. @pleath - while you are correct that the jitted code checks against the first type in the equivalent set, but if I read the code correctly, it doesn't index into the equivalent type cache or set to get the first type. It loads it from the guard, and we do record the last seen type on the guard (if it was, in fact, equivalent to the other types). |
@pleath , you are right, the guard value in online JITted code is same as type in slot[0]. So ideally But after re-reading the code and I think instead of looping to fetch the In reply to: 236775413 [](ancestors = 236775413) |
Now if we reach here that means we don't have any type in equivTypes. Better to put an assert that all types from 1 to < EQUIVALENT_TYPE_CACHE_SIZE are nullptr. Will help in catching issue where compaction failed. #Resolved Refers to: lib/Runtime/Language/JavascriptOperators.cpp:8236 in d633d84. [](commit_id = d633d84, deletion_comment = False) |
{ | ||
cache->nextEvictionVictim = 0; | ||
Output::Print(_u("EquivObjTypeSpec: Saving type in unused slot of equiv types cache. \n")); | ||
Output::Flush(); | ||
} |
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.
Better to use just one line - OUTPUT_TRACE(Js::EquivObjTypeSpecPhase, _u("EquivObjTypeSpec: Saving type in unused slot of equiv types cache. \n")); #WontFix
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.
Yeah, i will just follow the consistency here and keep it PHASE_TRACE1
In reply to: 73625408 [](ancestors = 73625408)
{ | ||
// just mark this as actual invalidated since there are no types | ||
// present | ||
guard->Invalidate(); |
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.
Correct me if this isn't true, but can InvalidateWhileSweeping() not be called when the current guard type is dead but other types in the equivalent cache are live? If that happens, do we really want to invalidate the guard?
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 was added more from perf perspective in CheckIfTypeIsEquivalent
where first thing that is done is
if (guard->GetValue() == 0)
{
return false;
}
So if the guard was invalidated while sweeping and none of equiv types are alive, we can return right away from CheckIfTypeIsEquivalent
. Without this, we would go further and fetch equivTypes[0]
and that would be nullptr
and that is when we would have returned.
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.
What I'm asking is, what prevents us from invalidating the guard and leaving it null while other equivalent types in the cache are alive?
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.
@kunalspathak pointed out the place where we avoid invalidating if there's still a live type.
#if DBG | ||
wasReincarnated = true; | ||
#endif | ||
this->value = 2; |
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.
Can you replace "2" with a defined constant?
|
@dotnet-bot test OSX osx_osx_debug_static please |
@dotnet-bot test Windows x86_release please |
@pleath , please review fix for cross context |
@dotnet-bot test Windows x64_release please |
👍 |
@dotnet-bot test OSX osx_osx_debug_static please |
`CheckIfTypeIsEquivalent` helper is called from JITted code to perform equivalent type check. It validates if the object's type is present in one of the 8 cached equivalent types and if yes, updates the guard value and return back to JITted code. If not, it takes slow path and verifies the prototype of object to that of cached type. However if guard is not valid, we would just bailout `BailOutFailedEquivalentTypeCheck`. Today in `PreSweepCallback` we would invalidate the guard if the underline type is not marked. However we could have invalid guard value but if there are alive types present in equivTypes[], we could use them to check if `type` in question matches any of them and if yes, update the guard value, so next time JIT code will be able to take advantage of it next time. On instrumenting code, noticed that there are high frequency that this happens (at least in acmeair) where we have valid equivTypes[], but we don'take take its advantage just because guard value is invalid. This gives approx. 2.5 % win in acmeair. Other benchmarks are flat. Also, I noticed that the cache eviction policy was kicking out valid caches despite of having empty slots in types[] where new type could have been stored. Modified the eviction logic to take advantage of empty slots before going to existing eviction logic. I have also added logic to compact the equivTypes by moving non-null types towards front of array. Test: Internal tools pass Perf: Standard benchmarks are almost flat with minor improvement in Octane, Kraken. Acme-air shows 2.5% win.
c8ba729
to
475df2c
Compare
Merge pull request #1368 from kunalspathak:typecheck `CheckIfTypeIsEquivalent` helper is called from JITted code to perform equivalent type check. It validates if the object's type is present in one of the 8 cached equivalent types and if yes, updates the guard value and return back to JITted code. If not, it takes slow path and verifies the prototype of object to that of cached type. However if guard is not valid, we would just bailout `BailOutFailedEquivalentTypeCheck`. Today in `PreSweepCallback` we would invalidate the guard if the underline type is not marked. However we could have invalid guard value but if there are alive types present in equivTypes[], we could use them to check if `type` in question matches any of them and if yes, update the guard value, so next time JIT code will be able to take advantage of it next time. On instrumenting code, noticed that there are high frequency that this happens (at least in acmeair) where we have valid equivTypes[], but we don'take take its advantage just because guard value is invalid. This gives approx. 2.5 % win in acmeair. Other benchmarks are flat. Also, I noticed that the cache eviction policy was kicking out valid caches despite of having empty slots in types[] where new type could have been stored. Modified the eviction logic to take advantage of empty slots before going to existing eviction logic. I have also added logic to compact the equivTypes by moving non-null types towards front of array. Test: Internal tools pass Perf: Standard benchmarks are almost flat with minor improvement in Octane, Kraken. Acme-air shows 2.5% win.
CheckIfTypeIsEquivalent helper is called from JITted code to perform equivalent type check.
It validates if the object's type is present in one of the 8 cached equivalent
types and if yes, updates the guard value and return back to JITted code.
If not, it takes slow path and verifies the prototype of object to that of cached
type. The cached type was always fetched from 0th index of array of cached equivalent types.
If this was nullptr, we were returning back to JITted code saying the type of object is not
equivalent to any cached types and then JITted code would bailout.
Some of the type caches can get collected in
PreSweepCallback
if types are not marked,hence the assumption to fetch cached type from 0th index is not valid because it might have
got collected while other cached types in the array are still around. Hence we should
fetch a non null equivalent type from cached array of types and then do subsequent slow
path checks.
This change reduces the bailouts of kind
BailOutFailedEquivalentTypeCheck
significantly.Perf:
In progressShows 2.5% win in acmeair. Others are flat.Test : Unit test pass, internal tools in progress.