Skip to content

Expand "return condition" into "return condition ? true : false" #107499

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

Merged
merged 45 commits into from
Mar 24, 2025

Conversation

EgorBo
Copy link
Member

@EgorBo EgorBo commented Sep 7, 2024

Closes #8363
Closes #65370

Various control-flow related optimizations do not kick in for return <condition> patterns properly, because JIT mostly works with BBJ_COND types of blocks. optOptimizeBool is one of few phases which does support BBJ_RETURN but that support takes extra (a lot of) code to support.

Instead, let's just expand all return condition into return condition ? true : false and then convert back to return condition where profitable.

Example:

static bool Test(int x)
{
    return x == 10 || x == 20 || x == 30;
}

Here, "recognize bit-test" optimization (fgRecognizeSwitch) does not kick in due to BBJ_RETURN:

; Assembly listing for method Foo:Test(int):ubyte (FullOpts)
       cmp      ecx, 10
       je       SHORT G_M12679_IG04
       cmp      ecx, 20
       jne      SHORT G_M12679_IG06
G_M12679_IG04:
       mov      eax, 1
       ret      
G_M12679_IG06:
       cmp      ecx, 30
       sete     al
       movzx    rax, al
       ret      
; Total bytes of code 26

This PR:

; Assembly listing for method Foo:Test(int):ubyte (FullOpts)
       cmp      ecx, 30
       ja       SHORT G_M12679_IG06
       mov      eax, 0x3FEFFBFF
       bt       eax, ecx
       jb       SHORT G_M12679_IG06
       mov      eax, 1
       ret      
G_M12679_IG06:
       xor      eax, eax
       ret
; Total bytes of code 24

@stephentoub hit this problem when he was trying to showcase various jit optimizations on simple methods and had to rewrite them into if-else.

As a follow-up I might clean up various phases relying on BBJ_RETURN

@ghost ghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Sep 7, 2024
@EgorBo
Copy link
Member Author

EgorBo commented Sep 8, 2024

@MihuBot

@EgorBo
Copy link
Member Author

EgorBo commented Sep 14, 2024

Will first address various size regressions in separate PRs before moving forward with this.

@xtqqczze
Copy link
Contributor

@EgorBo Should this also close #65370?

@EgorBo EgorBo reopened this Mar 20, 2025
@EgorBo EgorBo force-pushed the expand-return-cond branch from 458fe11 to 76cdec5 Compare March 23, 2025 13:41
@EgorBo EgorBo marked this pull request as ready for review March 23, 2025 22:27
@Copilot Copilot AI review requested due to automatic review settings March 23, 2025 22:27
@EgorBo
Copy link
Member Author

EgorBo commented Mar 24, 2025

PTAL @amanasifkhalid @AndyAyersMS cc @dotnet/jit-contrib
This PR does:

  1. Expands BBJ_RETURN(cond) into BBJ_COND(cond), basically: return X == 42 into if (X == 42) { return true; } else { return false; } so downstream optimizations can focus only on BBJ_COND blocks. It allowed me to remove a lot of existing code from optimizebools.cpp as redundant (no size diffs from that).
  2. Normally, tail-merging then merges all return true/false with other existing return true/false if any
  3. After most opts are done, there is an attempt to merge BBJ_COND(cond) back to BBJ_RETURN(cond) via fgFoldCondToReturnBlock + ifConversion phase.

Seems to be nice PerfScore improvements: diffs (size diffs aren't too big, but PerfScore are). Many size regressions are PerfScore improvements, some are missing opportunities for ifConversion phase.

if (!node->TypeIs(TYP_VOID))
{
printf("The tree is expected to be TYP_VOID:\n");
m_compiler->gtDispTree(node);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be an assert here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, oops, indeed

cond->gtFlags &= ~GTF_RELOP_JMP_USED;

// It's difficult to restore the original weight of the block, profile repair will handle it.
fgPgoConsistent = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

retTrueBb and retFalseBb don't have any successors, right? To maintain the profile, I think you only need to decrease the weight of each return block by the weight of the edge you just removed. We have some helpers to do this:

if (block->hasProfileWeight())
{
    retTrueBb->decreaseBBProfileWeight(trueEdge->getLikelyWeight());
    retFalseBb->decreaseBBProfileWeight(falseEdge->getLikelyWeight());
}

If you know which epilogue is becoming unreachable, then you can skip this computation. Also, if you use fgRemoveAllRefPreds above instead of fgRemoveRefPred, it will return the removed edge, so you don't need to do this computation before changing block into a BBJ_RETURN.

Regardless of how you do it, I think we should be able to maintain the profile throughout this transformation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

retTrueBb and retFalseBb don't have any successors, right?

@amanasifkhalid one of them is allowed to have multiple predecessors, hence, can't be removed. But yeah, I agree that we should be able to set the weight, let me see

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@amanasifkhalid have I addressed your feedback? in the recent commits

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, just one more thing below

EgorBo and others added 2 commits March 24, 2025 16:57
Co-authored-by: Aman Khalid <amankhalid@microsoft.com>

// Decrease the weight of the return blocks since we no longer have edges to them.
// Although, they still might be reachable from other blocks (at least one of them).
retTrueBb->decreaseBBProfileWeight(block->GetTrueEdge()->getLikelyWeight());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: These BB weight helpers will set the BBF_PROF_WEIGHT flag, so if we always update the profile weights here, we will have a mix of profile-derived and heuristic-derived block weights in non-PGO scenarios. We've been trying to avoid this so far by wrapping these updates in if (block->hasProfileWeight()) checks. Some day, I hope to turn on profile synthesis for non-PGO scenarios too so we can get rid of these checks and always maintain the profile, but that will require us to start benchmarking NativeAOT regularly before I feel good about making that change.

In the meantime, could you please wrap this with a hasProfileWeight check? Thanks!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, I see - added checks

#endif

// Early out if the current method is not returning a boolean.
if ((info.compRetType != TYP_UBYTE) || (genReturnBB != nullptr))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like we could proceed and then bail out later, if one of the successors is genReturnBB.

This comment was marked as outdated.

Copy link
Member Author

@EgorBo EgorBo Mar 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although, I misunderstood how it works. If am not mistaken, when we run out of some arbitrary limit of RETURNs in the merge-return phase, we merge the rest using this genReturnBb so you're right, we should not give up here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed via ce10e5c

EgorBo and others added 2 commits March 24, 2025 17:47
Co-authored-by: Aman Khalid <amankhalid@microsoft.com>
@EgorBo
Copy link
Member Author

EgorBo commented Mar 24, 2025

/ba-g "azurelinux.3 timeous"

@EgorBo EgorBo merged commit 551379a into dotnet:main Mar 24, 2025
105 of 108 checks passed
@EgorBo EgorBo deleted the expand-return-cond branch March 24, 2025 20:38
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
Projects
None yet
5 participants