Improve DCE in LIR liveness.#12892
Conversation
9ec809b to
7864a81
Compare
|
@dotnet-bot |
|
@BruceForstall @CarolEidt @dotnet/jit-contrib PTAL Essentially, this performs dead-store removal on SDSU temps as well as LclVars. Throughput results are good: when compared against the baseline, these changes improve throughput by .1% when using mean times for comparison and regress throughput by the same when using median times for comparison. This lines up pretty well with a reported 0.1% throughput improvement reported by callgrind on There are a couple small changes to the IR that are necessary in order to enable this:
I haven't been able to get |
|
@dotnet-bot test |
|
@dotnet-bot test Windows_NT x64 Debug Build and Test |
|
Internal diffs are good. The most common snippets we end up removing are Rarer are instances of dead helper calls, e.g. type tests: and allocations: Overall change as reported by SPMI: |
|
Can you note where you saw the dead isinst/newobj calls? These may be cases we could be cleaning up earlier. |
Presumably in the case of the unused compare we keep the indir and remove everything else? |
We only keep the indir if it might fault. In a number of cases we are able to remove the entire tree, including the indir.
@russellhadley thought the same. I will take a look. |
|
My guess is that we're better at removing dead code because we're always precompiling during |
|
@mikedn here's an example where we remove a compare but keep the load around (from becomes |
|
@dotnet-bot test Windows_NT arm64 Cross Debug Build |
|
|
||
| #define GTF_IND_NONFAULTING GTF_SET_FLAGS // GT_IND -- An indir that cannot fault. GTF_SET_FLAGS is not used on indirs. | ||
| #define GTF_IND_NONFAULTING 0x20000000 // GT_IND -- An indir that cannot fault. | ||
| #define GTF_IND_VOLATILE 0x40000000 // GT_IND -- the load or store must use volatile sematics (this is a nop on X86) |
There was a problem hiding this comment.
nit: the bits in the per-class sets are generally listed from high value to low
| } | ||
|
|
||
| bool GenTreeCall::IsSideEffectFree(Compiler* compiler, bool ignoreExceptions, bool ignoreCctors) const | ||
| { |
There was a problem hiding this comment.
- Please add normal function header comment
- Mention and call out relationship to gtNodeHasSideEffects() -- can that one use this one?
- Should this instead be named HasSideEffects() to avoid the pseudo-double-negative?
There was a problem hiding this comment.
Should this instead be named HasSideEffects() to avoid the pseudo-double-negative?
I would be in favor of that
|
|
||
| #define GTF_IND_NONFAULTING GTF_SET_FLAGS // GT_IND -- An indir that cannot fault. GTF_SET_FLAGS is not used on indirs. | ||
| #define GTF_IND_NONFAULTING 0x20000000 // GT_IND -- An indir that cannot fault. | ||
| #define GTF_IND_VOLATILE 0x40000000 // GT_IND -- the load or store must use volatile sematics (this is a nop on X86) |
There was a problem hiding this comment.
It's disconcerting that (at least) GTF_IND_VOLATILE/GTF_IND_UNALIGNED also apply to GT_INDEX, but are not commented as such.
There was a problem hiding this comment.
How is that possible? In the case of GTF_IND_VOLATILE, there would be an ambiguity with GTF_INX_STRING_LAYOUT were that the case. As far as I can tell these flags only apply to GT_IND.
| // | ||
| bool Compiler::fgComputeLifeLocal(VARSET_TP& life, VARSET_VALARG_TP keepAliveVars, GenTree* lclVarNode, GenTree* node) | ||
| void Compiler::fgComputeLifeTrackedLocalUse(VARSET_TP& life, LclVarDsc& varDsc, GenTreeLclVarCommon* node) | ||
| { |
There was a problem hiding this comment.
As usual, please add function headers here (and below)
| //------------------------------------------------------------------------ | ||
| // Compiler::fgComputeLifeLocal: compute the changes to local var liveness | ||
| // due to a use or a def of a local var and | ||
| // indicates wither the use/def is a dead |
| // | ||
| // Arguments: | ||
| // life - The live set that is being computed. | ||
| // keepAliveVars - The currents set of variables to keep alive |
| } | ||
|
|
||
| bool GenTreeCall::IsSideEffectFree(Compiler* compiler, bool ignoreExceptions, bool ignoreCctors) const | ||
| { |
There was a problem hiding this comment.
Should this instead be named HasSideEffects() to avoid the pseudo-double-negative?
I would be in favor of that
| @@ -945,9 +946,8 @@ struct GenTree | |||
| #define GTF_INX_REFARR_LAYOUT 0x20000000 // GT_INDEX -- same as GTF_IND_REFARR_LAYOUT | |||
There was a problem hiding this comment.
Since you've eliminated GTF_IND_REFARR_LAYOUT you should change the above comment.
There was a problem hiding this comment.
Also, I think that there should be a comment here describing the commonality between the GTF_INX flags and the GTF_IND flags, and explaining why some can overlap.
| { | ||
| // Is the variable already known to be alive? | ||
| if (VarSetOps::IsMember(this, life, varIndex)) | ||
| /* Mark variable as dead from here to its closest use */ |
There was a problem hiding this comment.
Please make this a C++ style comment, while you're here.
Also, I would rephrase this as simply "Remove variable from the live set." Talking about its closest use almost makes one think we're talking about its downstream use.
| return; | ||
| } | ||
|
|
||
| VARSET_TP varBit(VarSetOps::MakeEmpty(this)); |
There was a problem hiding this comment.
Again, I know this is pre-existing code, but varBit seems to be a strange name for a set that is likely to have more than one member. I would probably rename this fieldVars or something, but IMO it should at least be plural.
|
|
||
| // Removing a call does not affect liveness unless it is a tail call in a nethod with P/Invokes or | ||
| // is | ||
| // itself a P/Invoke, in which case it may affect the liveness of the frame root variable. |
There are three cases that are relevant here: - Defs of tracked locals - Uses of tracked locals - Appearances of untracked locals Each of these cases has been moved into its own function.
c1f1b97 to
44dbecb
Compare
| case GT_IND: | ||
| // We prefer printing R, V or U | ||
| if ((tree->gtFlags & (GTF_IND_REFARR_LAYOUT | GTF_IND_VOLATILE | GTF_IND_UNALIGNED)) == 0) | ||
| if ((tree->gtFlags & (GTF_IND_VOLATILE | GTF_IND_UNALIGNED)) == 0) |
There was a problem hiding this comment.
The comment above should be updated:
// We prefer printing V or U
| if (tree->gtCall.gtCallType == CT_HELPER) | ||
| GenTreeCall* const call = tree->AsCall(); | ||
| const bool ignoreExceptions = (flags & GTF_EXCEPT) == 0; | ||
| const bool ignoreCctors = (flags & GTF_IS_IN_CSE) != 0; // We can CSE helpers that run cctors. |
There was a problem hiding this comment.
I think that we also might be able to CSE expressions that throw exceptions.
There was a problem hiding this comment.
That is the case, yes. CSE does not pass the GTF_EXCEPT flag to this function.
There was a problem hiding this comment.
We could use a function header for this method describing that the caller passes in flags that control the behavior of this method. It seems a bit odd to me to use the flags argument on this way.
Alternatively we could pass in two values : 1) ignoreExceptions and 2 ignoreCctors
In particular: - Rather than only removing dead code as part of dead store removal, remove all side-effect-free nodes that either do not produce a value or produce a value that is unused. - When optimizing, set `fgStmtRemoved` in order to indicate that tracked lclVar uses or defs have been removed and liveness may need to be recalculated. Previously this flag was only set upon eliminating a dead store.
| if (tree->gtCall.gtCallType == CT_HELPER) | ||
| GenTreeCall* const call = tree->AsCall(); | ||
| const bool ignoreExceptions = (flags & GTF_EXCEPT) == 0; | ||
| const bool ignoreCctors = (flags & GTF_IS_IN_CSE) != 0; // We can CSE helpers that run cctors. |
There was a problem hiding this comment.
We could use a function header for this method describing that the caller passes in flags that control the behavior of this method. It seems a bit odd to me to use the flags argument on this way.
Alternatively we could pass in two values : 1) ignoreExceptions and 2 ignoreCctors
|
@dotnet-bot test Windows_NT x86 Checked Build and Test |
|
@mmitche any idea what's up with the RHEL packages not being found? |
|
@dotnet-bot test Windows_NT x86 Checked Build and Test |
|
@dotnet-bot |
In particular:
remove all side-effect-free nodes that either do not produce a value
or produce a value that is unused.
fgStmtRemovedin order to indicate that trackedlclVar uses or defs have been removed and liveness may need to be
recalculated. Previously this flag was only set upon eliminating a
dead store.