Conversation
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
* simplify IfConvertCheckStmts * small things
9792586 to
5c1616c
Compare
There was a problem hiding this comment.
Pull request overview
This PR simplifies the CoreCLR JIT if-conversion implementation by collapsing the prior multi-block flow search into a single, direct-flow check, under the assumption that Then/Else paths do not contain empty/chained blocks.
Changes:
- Replace the prior bounded “walk” of Then/Else chains with a single
IfConvertCheckFlow()check. - Simplify statement validation to operate on a single block (ignoring NOPs) rather than scanning a block chain to
m_finalBlock. - Simplify debug dumping and block removal to target only the identified Then/Else blocks.
| m_doElseConversion = trueBb->GetUniquePred(m_compiler) != nullptr; | ||
| m_finalBlock = m_doElseConversion ? trueBb->GetUniqueSucc() : trueBb; | ||
| m_mainOper = GT_STORE_LCL_VAR; | ||
|
|
||
| for (int thenLimit = 0; thenLimit < m_checkLimit; thenLimit++) | ||
| if (m_finalBlock == nullptr) | ||
| { | ||
| if (!IfConvertCheckInnerBlockFlow(thenBlock)) | ||
| assert(m_doElseConversion); | ||
| if (falseBb->KindIs(BBJ_RETURN) && trueBb->KindIs(BBJ_RETURN)) | ||
| { | ||
| // Then block is not in a valid flow. | ||
| return true; | ||
| m_mainOper = GT_RETURN; | ||
| } | ||
| BasicBlock* thenBlockNext = thenBlock->GetUniqueSucc(); | ||
|
|
||
| if (thenBlockNext == m_finalBlock) | ||
| { | ||
| // All the Then blocks up to m_finalBlock are in a valid flow. | ||
| m_flowFound = true; | ||
| if (thenBlock->KindIs(BBJ_RETURN)) | ||
| { | ||
| assert(m_finalBlock == nullptr); | ||
| m_mainOper = GT_RETURN; | ||
| } | ||
| else | ||
| { | ||
| m_mainOper = GT_STORE_LCL_VAR; | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| if (thenBlockNext == nullptr) | ||
| else | ||
| { | ||
| // Invalid Then and Else combination. | ||
| return false; | ||
| } | ||
|
|
||
| thenBlock = thenBlockNext; | ||
| } | ||
|
|
||
| // Nothing found. Still valid to continue. | ||
| return true; | ||
| } | ||
|
|
||
| //----------------------------------------------------------------------------- | ||
| // IfConvertFindFlow | ||
| // | ||
| // Find a valid if conversion flow from m_startBlock to a final block. | ||
| // There might be multiple Then and Else blocks in the flow - use m_checkLimit to limit this. | ||
| // | ||
| // Notes: | ||
| // Sets m_flowFound, m_finalBlock, m_doElseConversion and m_mainOper. | ||
| // | ||
| void OptIfConversionDsc::IfConvertFindFlow() | ||
| { | ||
| // First check for flow with no else case. The final block is the destination of the jump. | ||
| m_doElseConversion = false; | ||
| m_finalBlock = m_startBlock->GetTrueTarget(); | ||
| assert(m_finalBlock != nullptr); | ||
| if (!IfConvertCheckThenFlow() || m_flowFound) | ||
| { | ||
| // Either the flow is invalid, or a flow was found. | ||
| return; | ||
| } | ||
|
|
||
| // Look for flows with else blocks. The final block is the block after the else block. | ||
| m_doElseConversion = true; | ||
| for (int elseLimit = 0; elseLimit < m_checkLimit; elseLimit++) | ||
| { | ||
| BasicBlock* elseBlock = m_finalBlock; | ||
| if (elseBlock == nullptr || !IfConvertCheckInnerBlockFlow(elseBlock)) | ||
| { | ||
| // Need a valid else block in a valid flow . | ||
| return; | ||
| } | ||
|
|
||
| m_finalBlock = elseBlock->GetUniqueSucc(); | ||
|
|
||
| if (!IfConvertCheckThenFlow() || m_flowFound) | ||
| { | ||
| // Either the flow is invalid, or a flow was found. | ||
| return; | ||
| } | ||
| } | ||
| return falseBb->GetUniqueSucc() == m_finalBlock; |
There was a problem hiding this comment.
IfConvertCheckFlow now requires the Then/Else blocks to be direct predecessors of the merge (via GetUniqueSucc equality). However the JIT can introduce empty BBJ_ALWAYS fallthrough blocks (e.g., BBF_INTERNAL blocks from fgNormalizeEH), which previously would have been handled by walking a short chain. This makes the change unlikely to be truly “zero-diff” and can silently disable if-conversion for methods with EH normalization artifacts. Consider skipping/peeling empty fallthrough blocks (similar to SkipFallthroughBlocks in switchrecognition.cpp) or reintroducing a bounded walk so the optimization remains robust in the presence of empty blocks.
|
@a74nh @jakobbotsch What do you think? I guess the main question is whether this is a reasonable assumption to make in the first place: assert(m_startBlock->GetFalseTarget()->GetUniqueSucc() == m_finalBlock);
if (m_doElseConversion)
{
assert(m_startBlock->GetTrueTarget()->GetUniqueSucc() == m_finalBlock);
}Meaning no flow through empty blocks inside Then/Else cases. I simplified:
|
The original assumption was that the graph potentially could contain multiple blocks with a straight line flow between them. What could give that scenario?
Even if it's not a 100% correct assumption, sounds like we're still catching everything of relevance with your change. |
Do you mean something like this. Because it does get recognized. We just bail on profitability for the outer one: int a74nh(bool x, bool y)
{
if (x)
{
if (y)
{
return 1;
}
else
{
return 2;
}
}
else
{
return 3;
}
} mov eax, 3
mov ecx, 2
mov r10d, 1
test r8b, r8b
cmovne ecx, r10d
test dl, dl
cmovne eax, ecx
;; size=28 bbWeight=1 PerfScore 1.75
G_M25408_IG03: ;; offset=0x001C
ret Anyway so yeah, while there will still be Then/Else cases with multiple blocks that have linear flow this PR says: "Such cases won't be amendable to if-conversion anyway so let's bail in |
src/coreclr/jit/ifconversion.cpp
Outdated
There was a problem hiding this comment.
The empty blocks in this comment bloc (here and below) need removing
There was a problem hiding this comment.
Updated IR in the comment bloc
| GenTree* last = m_startBlock->lastStmt()->GetRootNode(); | ||
| noway_assert(last->OperIs(GT_JTRUE)); | ||
| m_cond = last->gtGetOp1(); | ||
| if (!m_cond->OperIsCompare()) |
There was a problem hiding this comment.
There are no longer any checks for the contents Op1 of JTRUE. You are making the assumption that it must be a comparison. Could you add assert(last->gtGetOp1()->OperIsCompare); just to be sure.
There was a problem hiding this comment.
Added assert(m_cond->OperIsCompare());
src/coreclr/jit/ifconversion.cpp
Outdated
| BasicBlock* thenBlock = m_startBlock->GetFalseTarget(); | ||
| m_doElseConversion = trueBb->GetUniquePred(m_compiler) != nullptr; | ||
| m_finalBlock = m_doElseConversion ? trueBb->GetUniqueSucc() : trueBb; | ||
| m_mainOper = GT_STORE_LCL_VAR; |
There was a problem hiding this comment.
Could you add an assert somewhere in this function to confirm the stmt is a GT_STORE_LCL_VAR
There was a problem hiding this comment.
ah - that is checked on line 384. Not sure if it'd be better placed in here.
There was a problem hiding this comment.
Yeah we can only assert that once we Checked the stmts. Or do you want to get m_mainOper from CheckStmts itself instead of CheckFlow. I was thinking of moving CheckStmts and CheckFlow into one function. Not sure what to name it though? It would return true if a if-conversion can be applied and assign all fields.
There was a problem hiding this comment.
I did that:
IfConvertCheckFlow+IfConvertCheckStmtsare bundled in a higher level function:IfConvertCheck(I am very open to renaming(s)). This will be good for JIT: Treat store in JTRUE block as the ElseOperation in if-conversion #124738- Instead of getting
m_mainOperfrom the flow check it now directly comes from the statements check. So we have:
m_mainOper = m_thenOperation.node->OperGet();
assert(m_mainOper == GT_RETURN || m_mainOper == GT_STORE_LCL_VAR);
There was a problem hiding this comment.
IfConvertCheck(I am very open to renaming(s))
it is ok - it does exactly what it says.
This will be good for JIT: Treat store in JTRUE block as the ElseOperation in if-conversion #124738
If you're continuing down this road then maybe you want to consider more than one statement inside the if.
GCC will if convert https://godbolt.org/z/18YqGbE43, but not if you uncomment the a3 statement.
* add 'assert(m_cond->OperIsCompare())' * combine IfConvertCheckFlow + IfConvertCheckStmts into new IfConvertCheck * get m_mainOper from m_thenOperation instead of flow check
Zero-diff change that simplifies if-conversion based on the assumption that there are no empty blocks inside the Then and Else case. See: #125072 (comment)