The dependent struct promotion was initially added as a workaround for illegal independent struct promotion cases that we found too late. The main reason for that was that we ran fgMarkAddressExposedLocals after fgPromoteStructs, so when we understood that a lclVar has addrTaken fields etc. it was too late to cancel the promotion.
After fgMarkAddressExposedLocals was cleaned and rewritten by @mikedn it became possible to move fgMarkAddressExposedLocals phase before fgPromoteStructs. It will allow us to remove dependent struct promotion and buggy code that deals with it (for example in fgMorphStructField). This will give us a better TP (because we get rid of many tracked variables that we do not fully utilize) and cleaner code.
Also, it is possible to improve our CQ by the way. I want to make the change that deletes dependent struct diff-free; in order to do that, I am planning to teach our JIT to work equally with dependent promoted and not-promoted fields.
If right now you disable dependent struct promotion you will see some diffs in System.Private.CoreLib, both improvements and regressions:
diff is an improvement.
-10 : System.Private.CoreLib.dasm (0.00% of base)
31 total methods with size differences (27 improved, 4 regressed), 26200 unchanged.
The regressed methods are doing CSE or asserting propagation for dependent struct fields better.
The main improvement case is where we replace small LCL_FIELD with LCL_VAR for arguments and then do lvNormalizeOnLoad replacing LCL_VAR bool with CAST int<-bool<-int (LCL_VAR int); on x64 we generate 2 instruction when we promote such field:
mov eax, dword ptr [rbp-44H]
movsx rax, ax
when we do not do struct promotion for this struct we keep this field access as LCL_FIELD and generate only one instruction:
movsx rax, word ptr [rbp-44H]
Does somebody remember case when we need to do this replacement?
https://github.com/dotnet/coreclr/blob/0aab97266ea6620a17a20804c7aebfcdbd37a5b0/src/jit/morph.cpp#L6017-L6027
I did not hit any asserts when I removed it but got some regressions (as well as expected improvements) because OptimizeConstCompare was not able to optimize EQ bool lcl_var, int const as it is able to optimize EQ cast int<-bool<-int (LCL_VAR int), int const. that can be easily fixed.
So my plan is to fix CQ/code size regression caused by dependent struct promotion step by step, then start to teach assertion propagation and CSE not to rely on lvPromote flag. As bonus it will allow us to do the same optimizations for struct fields that we can't promote now, for example if there are more than 4 fields in the struct but one is used often or in a loop.
Bonus: another case where we do a kind of dependent struct promotion is fgRetypeImplicitByRefArgs:
// If the promotion is dependent, the promoted temp would just be committed
// to memory anyway, so we'll rewrite its appearances to be indirections
// through the pointer parameter, the same as we'd do for this
// parameter if it weren't promoted at all (otherwise the initialization
// of the new temp would just be a needless memcpy at method entry).
bool undoPromotion = (lvaGetPromotionType(newVarDsc) == PROMOTION_TYPE_DEPENDENT) ||
(varDsc->lvRefCnt(RCS_EARLY) <= varDsc->lvFieldCnt);
The right analysis for x64 should make decision for each field separately and for code size purposes should compare fieldDsc->lvRefCnt(RCS_EARLY) >= 2. Or at least varDsc->lvFieldCnt * 5 + varDsc->lvRefCnt(RCS_EARLY) < 4 * varDsc->lvRefCnt(RCS_EARLY) => varDsc->lvFieldCnt * 5 < 3 * varDsc->lvRefCnt(RCS_EARLY) that means that it is profitable to spend 5 instruction in the scratch block to copy each fields and then access them with 1 instruction instead of 4. However, if you change it now it will cause few regressions because CSE will work worse.
P.s. I was not able to do the description short and sweet, but hope it is readable.
category:cq
theme:structs
skill-level:expert
cost:extra-large
impact:large
The dependent struct promotion was initially added as a workaround for illegal independent struct promotion cases that we found too late. The main reason for that was that we ran
fgMarkAddressExposedLocalsafterfgPromoteStructs, so when we understood that a lclVar has addrTaken fields etc. it was too late to cancel the promotion.After
fgMarkAddressExposedLocalswas cleaned and rewritten by @mikedn it became possible to movefgMarkAddressExposedLocalsphase beforefgPromoteStructs. It will allow us to remove dependent struct promotion and buggy code that deals with it (for example infgMorphStructField). This will give us a better TP (because we get rid of many tracked variables that we do not fully utilize) and cleaner code.Also, it is possible to improve our CQ by the way. I want to make the change that deletes dependent struct diff-free; in order to do that, I am planning to teach our JIT to work equally with dependent promoted and not-promoted fields.
If right now you disable dependent struct promotion you will see some diffs in
System.Private.CoreLib, both improvements and regressions:The regressed methods are doing CSE or asserting propagation for dependent struct fields better.
The main improvement case is where we replace small
LCL_FIELDwithLCL_VARfor arguments and then dolvNormalizeOnLoadreplacingLCL_VAR boolwithCAST int<-bool<-int (LCL_VAR int); on x64 we generate 2 instruction when we promote such field:when we do not do struct promotion for this struct we keep this field access as
LCL_FIELDand generate only one instruction:Does somebody remember case when we need to do this replacement?
https://github.com/dotnet/coreclr/blob/0aab97266ea6620a17a20804c7aebfcdbd37a5b0/src/jit/morph.cpp#L6017-L6027
I did not hit any asserts when I removed it but got some regressions (as well as expected improvements) because
OptimizeConstComparewas not able to optimizeEQ bool lcl_var, int constas it is able to optimizeEQ cast int<-bool<-int (LCL_VAR int), int const.that can be easily fixed.So my plan is to fix CQ/code size regression caused by dependent struct promotion step by step, then start to teach assertion propagation and CSE not to rely on
lvPromoteflag. As bonus it will allow us to do the same optimizations for struct fields that we can't promote now, for example if there are more than 4 fields in the struct but one is used often or in a loop.Bonus: another case where we do a kind of dependent struct promotion is
fgRetypeImplicitByRefArgs:The
rightanalysis for x64 should make decision for each field separately and for code size purposes should comparefieldDsc->lvRefCnt(RCS_EARLY) >= 2. Or at leastvarDsc->lvFieldCnt * 5 + varDsc->lvRefCnt(RCS_EARLY) < 4 * varDsc->lvRefCnt(RCS_EARLY)=>varDsc->lvFieldCnt * 5 < 3 * varDsc->lvRefCnt(RCS_EARLY)that means that it is profitable to spend 5 instruction in the scratch block to copy each fields and then access them with 1 instruction instead of 4. However, if you change it now it will cause few regressions because CSE will work worse.P.s. I was not able to do the description short and sweet, but hope it is readable.
category:cq
theme:structs
skill-level:expert
cost:extra-large
impact:large