This is an archive of the discontinued LLVM Phabricator instance.

[CHR] Fix up phi nodes with unreachable predecessors (PR64594)
ClosedPublic

Authored by nikic on Aug 10 2023, 7:42 AM.

Details

Summary

If a block in the CHR region has an unreachable predecessor, then there will be no edge from that predecessor to the newly cloned block. However, a phi node entry for it will be left behind. Make sure that these incoming blocks get dropped as well.

Fixes https://github.com/llvm/llvm-project/issues/64594.

Diff Detail

Event Timeline

nikic created this revision.Aug 10 2023, 7:42 AM
Herald added a project: Restricted Project. · View Herald TranscriptAug 10 2023, 7:42 AM
nikic requested review of this revision.Aug 10 2023, 7:42 AM
Herald added a project: Restricted Project. · View Herald TranscriptAug 10 2023, 7:42 AM
nikic retitled this revision from [CHR] Fix up phi nodes with unreachable predecessors to [CHR] Fix up phi nodes with unreachable predecessors (PR64594).Aug 10 2023, 7:43 AM
nikic edited the summary of this revision. (Show Details)
nikic updated this revision to Diff 549041.Aug 10 2023, 7:47 AM

Fix typo in comment.

kazu accepted this revision.Aug 10 2023, 9:34 AM

LGTM. Thanks!

This revision is now accepted and ready to land.Aug 10 2023, 9:34 AM
nikic reopened this revision.Aug 11 2023, 2:08 AM
This revision is now accepted and ready to land.Aug 11 2023, 2:08 AM
nikic updated this revision to Diff 549295.Aug 11 2023, 2:19 AM

Add -verify-region-info to test, adjust verifier to allow unreachable predecessor. This also resolves a FIXME in a StructurizeCFG test.

RegionInfo ignores blocks not connected to the dominator tree during analysis, so we should also ignore them during verification.

A possible alternative here would be that we shouldn't create regions with unreachable predecessors, though looking at the construction algorithm I'm not clear on how one would do that.

nikic requested review of this revision.Aug 11 2023, 2:19 AM

Adding some more people who might have an opinion on how RegionInfo is supposed to handle unreachable blocks.

I can't think of any better way to handle this overall, given the way regions and dead basic blocks work.

Stuff like this makes me tempted to say we should try to forbid unreachable blocks in IR; it's not really that much simpler overall, but the complexity would be distributed in a way that's less likely to lead to rare crashes.

llvm/lib/Transforms/Instrumentation/ControlHeightReduction.cpp
1786

I think if (!NewBB->phis().empty()) or something like would be more readable? It's sort of confusing to have a for loop with only one iteration.

Is iterator invalidation actually possible here? That would imply the block has no live predecessors, which shouldn't be possible for a block in a region. Maybe better to write a generic implementation in case someone tries to copy it elsewhere, though.

This is quadratic in the number of predecessors, for multiple reasons: calling removeIncomingValue with a BasicBlock* implies a scan over all the predecessors, and then actually removing the value requires another linear scan to fill the gap. But not sure if that's likely to matter.

nikic updated this revision to Diff 550239.Aug 15 2023, 3:29 AM

Avoid single-iteration loop.

nikic added a comment.Aug 15 2023, 3:39 AM

I can't think of any better way to handle this overall, given the way regions and dead basic blocks work.

Stuff like this makes me tempted to say we should try to forbid unreachable blocks in IR; it's not really that much simpler overall, but the complexity would be distributed in a way that's less likely to lead to rare crashes.

Interesting idea, sounds plausible to me. We probably don't have all that many transforms that can orphan blocks and don't clean them up as a matter of course.

llvm/lib/Transforms/Instrumentation/ControlHeightReduction.cpp
1786

The iterator invalidation I'm concerned about here is the case where multiple predecessors need to be removed. I don't think it's valid to iterate PHINode::blocks() while also removing incoming blocks.

We do have generic implementations of the general concept -- what you'd usually do is iterate over predecessors() and call removePredecessor(). The problem is that at this point the CFG is in a somewhat inconsistent state, with the new block cloned but edges not rewritten yet, so predecessors() will not do the right thing and removePredecessor() will assert.

The removal is indeed quadratic, but I don't think it really matters in this case. I think with the APIs we have the only way to make it linear would be to create new PHINodes instead of changing existing ones.

efriedma added inline comments.Aug 15 2023, 11:05 AM
llvm/lib/Transforms/Instrumentation/ControlHeightReduction.cpp
1786

I don't think it's valid to iterate PHINode::blocks() while also removing incoming blocks.

Oh, sure. I got confused because of the use of make_early_inc_range.

I think with the APIs we have the only way to make it linear would be to create new PHINodes instead of changing existing ones.

It probably makes sense to add an API similar to std::remove_if to PHINode... but I agree it's unlikely to matter.

efriedma accepted this revision.Aug 15 2023, 1:21 PM

LGTM

llvm/lib/Transforms/Instrumentation/ControlHeightReduction.cpp
1786

Sure, leaving the new API for a followup sounds fine.

This revision is now accepted and ready to land.Aug 15 2023, 1:21 PM
This revision was landed with ongoing or failed builds.Aug 16 2023, 1:09 AM
This revision was automatically updated to reflect the committed changes.
llvm/test/Transforms/PGOProfile/chr-dead-pred.ll