Uh oh!
There was an error while loading. Please reload this page.
JIT: Set GTF_ORDER_SIDEEFF for some nodes with invisible dependencies - #78698
Conversation
We have a few places where we create the pattern "COMMA(some check, some value)". In some of these cases there may not be any visible dependency (e.g. use of a defined value) which makes the dependency invisible to the JIT. If the value is safe to compute only because of the check (for example, a bounds check + indexing operation), and if the value otherwise has no side effects, then nothing prevented the backend or optimizations from reordering these nodes. Fixdotnet#78554
ghost
commented
Nov 22, 2022
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch Issue DetailsWe have a few places where we create the pattern "COMMA(some check, some value)". In some of these cases there may not be any visible dependency (e.g. use of a defined value) which makes the dependency invisible to the JIT. If the value is safe to compute only because of the check (for example, a bounds check + indexing operation), and if the value otherwise has no side effects, then nothing prevented the backend or optimizations from reordering these nodes. Fix #78554
|
| GenTree* lclVar = gtNewLclvNode(lclNum, objRefType); | ||
| GenTree* nullchk = gtNewNullCheck(lclVar, compCurBB); | ||
| nullchk->gtFlags |= GTF_ORDER_SIDEEFF; |
a74nh
commented
Nov 22, 2022
Is it worth adding the test case from #78561 ? |
jakobbotsch
commented
Nov 22, 2022
Yep, will do that |
jakobbotsch
commented
Nov 23, 2022
Diffs were not as bad as I would've feared. The change to check Other than that regressions fell into these categories:
|
jakobbotsch
commented
Nov 23, 2022
Diffs are pretty minimal now, the larger xarch ones are in some of the massive cse tests. This should unblock #77728. cc @dotnet/jit-contrib @a74nh PTAL @EgorBo@SingleAccretion |
Uh oh!
There was an error while loading. Please reload this page.
| } | ||
| if (helperNode != nullptr) | ||
| { | ||
| helperNode->gtFlags |= GTF_ORDER_SIDEEFF; |
There was a problem hiding this comment.
Why do we need to set the ordering effect on these?
The calls should not be reorderable with GTF_GLOB_REF-tagged field static access by themselves.
There was a problem hiding this comment.
Hmm yes, I can revert this one. I just looked for commas and applied this on the op1 in any place there was no directly visible dependency between the operands.
| nullcheck->gtFlags |= GTF_ORDER_SIDEEFF; | ||
| GenTree* result = gtNewOperNode(GT_COMMA, TYP_BYREF, nullcheck, boxPayloadAddress); |
There was a problem hiding this comment.
It is not obvious what the ordering effect here protects against -- what would a theoretical example of bad transformation look like?
There was a problem hiding this comment.
I think I can revert this one too given that boxPayloadAddress is known to be very small when cloneOperand is null.
If it wasn't then early prop would probably be able to do something illegal here if it removed this null check due to an upcoming null check, causing us to form some random byref.
There was a problem hiding this comment.
Hmm, I'm not sure after all. Without setting this then if we did the optimization in #71435 we could potentially remove these null checks and turn a NRE into an AV when accessing a field right around the explicit null check threshold. Consider something like:
ASGLCL_VAR byref V00COMMA byref // added hereNULLCHECK// node ALCL_VAR ref V01ADD byref
LCL_VAR ref V01CNS_INT long 8ASGLCL_VAR int V02IND int // node BADDLCL_VAR byref V00CNS_INT long 0xFFFCASGLCL_VAR int V03IND int // node CLCL_VAR ref V01The optimization would reason as follows: the node A null check is unnecessary because node B only has the same side effects as the null check (throwing NRE), and node C subsumes node A. However, node B will actually AV without node A.
There was a problem hiding this comment.
I would classify this as the optimization being wrong, since we can only consider NRE to be "true" NRE and not "NRE but maybe AV" with small offsets (we do the checks against the max null check offset in other places to differentiate these two cases, though not everywhere).
More fundamentally, fixing this with an ordering effect seems... arbitrary? What is the ordering dependency?
There was a problem hiding this comment.
We've talked with @jakobbotsch on Discord and concluded the ordering here is needed because "almost null" byrefs must never occur on dynamically reachable paths, so swapping the ADD with the null check would effectively introduce UB into the program.
It has also been determined that our handling around GTF_ORDER_SIDEEFF is a bit lacking, and it's meaning a bit unclear. We've settled on the "only fully side-effect-less nodes can be reordered with ordered nodes". This then implies both the ADD and the null check need to be tagged as ordered.
There was a problem hiding this comment.
It has also been determined that our handling around
GTF_ORDER_SIDEEFFis a bit lacking, and it's meaning a bit unclear. We've settled on the "only fully side-effect-less nodes can be reordered with ordered nodes"
It might be useful to add the meaning as a comment. Maybe next to the definition in gentree.h? The current "sub-expression has a re-ordering side effect" is a little lacking.
There was a problem hiding this comment.
I agree we should have some more detailed docs on what the effect flags mean and how they are assumed to be set, but I think it would be better to add a section in https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/jit/ryujit-tutorial.md and refer to that. I will aim to do a write-up of that soon.
jakobbotsch
commented
Nov 24, 2022
jakobbotsch
commented
Nov 25, 2022
/azp run runtime-coreclr jitstress, runtime-coreclr libraries-jitstress, Fuzzlyn |
|
Azure Pipelines successfully started running 3 pipeline(s). |
We have a few places where we create the pattern "COMMA(some check, some value)". In some of these cases there may not be any visible dependency (e.g. use of a defined value) which makes the dependency invisible to the JIT.
If the value is safe to compute only because of the check (for example, a bounds check + indexing operation), and if the value otherwise has no side effects, then nothing prevented the backend or optimizations from reordering these nodes.
A particular problem we may have is around array indexing and bounds checks. Creating an arbitrary illegal byref is not allowed, but to the JIT this node is just a normal node that is completely free of any side effects. Before this change nothing was preventing us from reordering the bounds checks with the computation of the array element.
Fix#78554