-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
JIT: Add explicit successor for BBJ_COND false branch #95773
Conversation
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch Issue DetailsPart of #93020. Currently, if a For now,
|
src/coreclr/jit/block.h
Outdated
@@ -573,11 +576,18 @@ struct BasicBlock : private LIR::Range | |||
|
|||
void SetNext(BasicBlock* next) | |||
{ | |||
// TODO: remove |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant to delete this; will get rid of it in the next revision.
src/coreclr/jit/fgdiagnostic.cpp
Outdated
@@ -2957,6 +2957,13 @@ void Compiler::fgDebugCheckBBlist(bool checkBBNum /* = false */, bool checkBBRef | |||
|
|||
maxBBNum = max(maxBBNum, block->bbNum); | |||
|
|||
// BBJ_COND's normal (false) jump target is expected to be the next block | |||
// TODO: Allow bbNormalJumpDest to diverge from bbNext |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A little tip for writing future TODO
s: in ongoing work, it is handy to add a unique postfix to a TODO
, e. g. TODO-NoFallThrough
, s. t. they can be text-searched for easily afterwards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the tip; I realized this might be a better approach about halfway through... I'll update the TODOs I introduced to make them easier to Ctrl+F when I remove them in the next PR.
CC @dotnet/jit-contrib, @AndyAyersMS PTAL -- sorry for the wait on this. No diffs except for TP impact. |
What is the terminology we want to use for the two BBJ_COND branches? With this PR we now have "bbNormalJumpDest" indicating the "false" path and "bbJumpDest" indicating the "true" path. Neither of these words seem satisfactory to me, especially "normal". It seems like we should have "bbFalseJumpDest" and "bbTrueJumpDest" for BBJ_COND. For cases with only a single destination (e.g., BBJ_ALWAYS), just "bbJumpDest" seems fine. We over-emphasize the word "jump"; maybe we can remove it from all those places, instead having "bbFalseDest", "bbTrueDest", "bbDest", "bbSwtDest", "bbEhfDest", "bbKind", "BBKinds", etc. And maybe "Dest" should instead be "Target": "bbFalseTarget", "bbTrueTarget", "bbTarget", "bbSwtTarget", "bbEhfTarget". I think this is my preferred option. @dotnet/jit-contrib Comments? |
FWIW, here's the origin of the "normal" terminology: #93772 (comment). I agree it would be best to use "True/False", the problem was/is that we use |
I agree this would be a good time to rename these members.
For I concur with your other naming suggestions, @BruceForstall. |
Yes, this is what I was thinking. Yes, there would be new helpers that would assert on BBJ_COND and return/set the new union member. This probably means various "switch" cases where BBJ_COND behavior re-uses the same "branch" behavior and |
I agree true/false makes more sense, as abstractly before block layout all control flow transfers are jumps. Ultimately, I would like to see the blocks refer to |
I presume you're stating this as a long-term goal, and not something we should consider for this, or a short-term PR? Thus, we can evolve in that direction but still take name changes and block pointer implementation as proposed (if agreed to)? |
I've renamed the I apologize for the size of this change; at the very least, it does not produce any asmdiffs locally. |
I think splitting these overloads to separate methods would make sense -- e.g. |
src/coreclr/jit/block.h
Outdated
return (bbFalseTarget == target); | ||
} | ||
|
||
void SetCondKindAndTarget(BasicBlock* target) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't exactly roll off the tongue -- why not SetCond
? Eventually we would hopefully end up with just SetCond(BasicBlock* falseTarget, BasicBlock* trueTarget)
, SetSwitch(BBswtDesc* swtTarget)
and so on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can shorten the names of all the SetKind...
variants. Would you be ok with SetKindAndTarget
being left as-is? I'm not sure how we can intuitively shorten that one while still conveying it's setting the jump kind and target.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think SetKindAndTarget
makes sense -- it's sort of a low-level operation, while these other ones are more high-level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few small suggestions. Feel free to defer those to a subsequent PR.
src/coreclr/jit/block.h
Outdated
} | ||
|
||
BBswtDesc* GetJumpSwt() const | ||
BBswtDesc* GetSwitchTarget() const |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: switches have multiple targets, so maybe?
BBswtDesc* GetSwitchTarget() const | |
BBswtDesc* GetSwitchTargets() const |
src/coreclr/jit/block.h
Outdated
} | ||
|
||
BBehfDesc* GetJumpEhf() const | ||
BBehfDesc* GetEhfTarget() const |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar nit
BBehfDesc* GetEhfTarget() const | |
BBehfDesc* GetEhfTargets() const |
src/coreclr/jit/block.h
Outdated
assert(HasInitializedJumpDest()); | ||
return (bbJumpDest == jumpDest); | ||
// BBJ_COND should use TrueTargetIs | ||
assert(!KindIs(BBJ_COND)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like you should also assert for BBJ_SWITCH
and BBJ_EHFINALLYRET
in these (here and elsewhere).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we're already implicitly asserting for BBJ_SWITCH
and BBJ_EHFINALLYRET
by asserting HasInitializedTarget
, as that asserts HasTarget
, which checks KindIs(BBJ_ALWAYS, BBJ_CALLFINALLY, BBJ_COND, BBJ_EHCATCHRET, BBJ_EHFILTERRET, BBJ_LEAVE)
. But that's a lot of indirection, so I can explicitly assert for BBJ_SWITCH
and BBJ_EHFINALLYRET
, too.
src/coreclr/jit/block.h
Outdated
} | ||
|
||
void SetJumpEhf(BBehfDesc* jumpEhf) | ||
void SetEhfTarget(BBehfDesc* ehfTarget) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
void SetEhfTarget(BBehfDesc* ehfTarget) | |
void SetEhfTargets(BBehfDesc* ehfTargets) |
src/coreclr/jit/block.h
Outdated
unsigned bbTargetOffs; // PC offset (temporary only) | ||
BasicBlock* bbTarget; // basic block | ||
BasicBlock* bbTrueTarget; // BBJ_COND jump target when its condition is true (alias for bbTarget) | ||
BBswtDesc* bbSwtTarget; // switch descriptor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BBswtDesc* bbSwtTarget; // switch descriptor | |
BBswtDesc* bbSwtTargets; // switch descriptor |
src/coreclr/jit/block.h
Outdated
BasicBlock* bbTarget; // basic block | ||
BasicBlock* bbTrueTarget; // BBJ_COND jump target when its condition is true (alias for bbTarget) | ||
BBswtDesc* bbSwtTarget; // switch descriptor | ||
BBehfDesc* bbEhfTarget; // BBJ_EHFINALLYRET descriptor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BBehfDesc* bbEhfTarget; // BBJ_EHFINALLYRET descriptor | |
BBehfDesc* bbEhfTargets; // BBJ_EHFINALLYRET descriptor |
src/coreclr/jit/block.h
Outdated
BasicBlock* GetTarget() const | ||
{ | ||
// BBJ_COND should use GetTrueTarget | ||
assert(!KindIs(BBJ_COND)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like you should also assert for BBJ_SWITCH and BBJ_EHFINALLYRET in these (here and elsewhere).
src/coreclr/jit/block.h
Outdated
void SetTarget(BasicBlock* target) | ||
{ | ||
// BBJ_COND should use SetTrueTarget | ||
assert(!KindIs(BBJ_COND)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like you should also assert for BBJ_SWITCH and BBJ_EHFINALLYRET in these (here and elsewhere).
// `block` (inclusive). Thus, we need to ensure there is a label on the lexical fall-through | ||
// block, even if one is not otherwise needed, to be able to calculate the size of this | ||
// loop (loop size is calculated by walking the instruction groups; see emitter::getLoopSize()). | ||
|
||
if (block->GetJumpDest()->isLoopAlign()) | ||
if (block->GetTarget()->isLoopAlign()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This kind of code duplication is a bit unfortunate. Obviously early on there is a distinction for BBJ_COND but later on there isn't...
One half-baked idea is to have some variant of BBJ_COND that only appears late, once we allow fall throughs, and can't appear earlier. But let's just leave this as is for now.
@AndyAyersMS thank you for the review; I've applied your feedback. The new asserts you suggested triggered in |
OSX arm64 NativeAOT failure looks unrelated. |
I hit asserts with [MethodImpl(MethodImplOptions.NoInlining)]
static int abs(int num) => num < 0 ? -num : num; hits Assert failure(PID 21588 [0x00005454], Thread: 51020 [0xc74c]): Assertion failed 'KindIs(BBJ_COND)' in 'Program:abs(int):int' during 'If conversion' (IL size 9; hash 0x993542c0; FullOpts)
File: C:\dev\dotnet\runtime4\src\coreclr\jit\block.h Line: 661
Image: D:\dev\core_roots\6c7e6e2e50550737260e40f0b472158bc15a6f82\corerun.exe when |
@jakobbotsch Thank you for letting me know, taking a look now... |
…#95934) In #95773, I incorrectly assumed m_startBlock would always be a BBJ_COND in OptIfConversionDsc::IfConvertDump, but it can be converted to a BBJ_ALWAYS before being dumped, thus hitting an assert when m_startBlock->GetTrueTarget() is called. This is fixed by calling the correct target accessor method depending on the type of m_startBlock. (Note that IfConvertDump is only called if dumps are enabled, hence why this assert wasn't initially hit in CI.)
Part of #93020. Currently, if a
BBJ_COND
block'sbbJumpDest
branch is not taken, control falls through into the next block. Because we plan to remove this fall-through requirement,BBJ_COND
blocks need a pointer to their "not-taken" branch target, as the target may no longer be the next block. This new pointer,bbNormalJumpDest
, may also be used in the future to represent the finally continuation of aBBJ_CALLFINALLY/BBJ_ALWAYS
pair, once this pattern is consolidated into one block with no implicit fall-through via #95355.For now,
bbNormalJumpDest
models current behavior by always pointing to the next block. Follow-up work will introduce behavioral changes by allowingbbNormalJumpDest
to diverge frombbNext
.