This is an archive of the discontinued LLVM Phabricator instance.

[StructurizeCFG] Fix region nodes ordering
ClosedPublic

Authored by ekatz on Apr 28 2020, 1:45 PM.

Details

Summary

This is a reimplementation of the orderNodes function, as the old implementation didn't take into account all cases.

Fix PR41509

Diff Detail

Event Timeline

ekatz created this revision.Apr 28 2020, 1:45 PM

This needs more tests. There are a bunch of decisions being taken during the reordering, but the test peresented simply exercises the case of one nested loop inside another. For example, what about two inner loops L1 and L2, where L1 has an exit that reaches the entry of L2? There might be other interesting cases too.

We actually introduced a different pass recently, called "UnifyLoopExits" to circumvent the same problem in the structurizer. You can see it optionally invoked in the AMDGPU backend. The idea is to transform each loop to have a single exit block, which makes it look like a separate region; since the structurizer is a region pass, it no longer sees a loop nest and thus cannot get confused about the nesting.

The current patch can provide a different, more precise way to solve the same problem. But please do take a look at the tests that use UnifyLoopExits. They are under test/Transforms/UnifyLoopExits and test/Transforms/StructurizeCFG/workarounds.

sameerds added inline comments.Apr 29 2020, 10:26 AM
llvm/lib/Transforms/Scalar/StructurizeCFG.cpp
151

Why return a pointer here? As far as I could see, the return value is never checked for nullptr in the places where this function is called.

384–416

Should this comment be removed now? The problem with the backedges of the outer loops is addressed in this change, right?

394–396

It's very hard to read these two nested loops, especially with respect to their exit conditions. If I understand correctly, the inner loop at line 390 keeps executing until the correct number of blocks is found for a given loop. Can this condition be placed in the while () itself?

429

It seems the only purpose of this object is to invoke the "run()" method. Could this be just split out into a bunch of functions instead? All the class members can be declared right here, and the member functions of the current class can be rewritten to take necessary structures as arguments.

llvm/test/Transforms/StructurizeCFG/pr41703.ll
4 ↗(On Diff #260753)

Can you please add a comment indicating which blocks end up in the wrong order? This will be useful when revisiting this testcase at some future time.

ekatz marked 5 inline comments as done.Apr 30 2020, 4:02 AM
ekatz added inline comments.
llvm/lib/Transforms/Scalar/StructurizeCFG.cpp
151

Because I need to update the value later.
In any case, I will remove this function, as it can be simplified.

384–416

It is still true, it should be treated as an intro for the following algorithm (explaining why we don't stop here).
I'll add a sentence to make it clearer.

394–396

I'll rearrange those loops.

429

I can reduce it to a single utility function, and then it will make even less sense to keep it. Then I'll remove it.

llvm/test/Transforms/StructurizeCFG/pr41703.ll
4 ↗(On Diff #260753)

sure

ekatz updated this revision to Diff 261184.Apr 30 2020, 4:03 AM
ekatz edited the summary of this revision. (Show Details)

Done requested changes.

foad added a subscriber: foad.Apr 30 2020, 9:24 AM

It seems the new commit is an incremental update on the previous commit. Can you please submit a single squashed commit, so that the change can be compared against the original state of the trunk?

Also, the following tests were created specifically to demonstrate the wrong ordering:

  • test/Transforms/StructurizeCFG/workarounds/needs-unified-loop-exits.ll
  • test/Transforms/StructurizeCFG/workarounds/needs-fr-ule.ll

If the proposed change is correct, then the example in these tests will be structurized correctly even after removing the "-unify-loop-exits" argument. These tests should also be checked and then updated accordingly.

In fact, it would be nice to update the following tests. But they have a very confusing history and they are specific to AMDGPU. I can volunteer to do this later as a follow up.

  • test/Transforms/StructurizeCFG/AMDGPU/backedge-id-bug.ll
  • test/Transforms/StructurizeCFG/AMDGPU/backedge-id-bug-xfail.ll
ekatz added a comment.May 2 2020, 5:21 AM

It seems the new commit is an incremental update on the previous commit. Can you please submit a single squashed commit, so that the change can be compared against the original state of the trunk?

I am not sure what do you mean. In this page in the Phabricator, under [Revision Contents]->[History] you can choose which of the patch revisions to diff. The default is "Base" against the latest "Diff no.".

Also, the following tests were created specifically to demonstrate the wrong ordering:

  • test/Transforms/StructurizeCFG/workarounds/needs-unified-loop-exits.ll
  • test/Transforms/StructurizeCFG/workarounds/needs-fr-ule.ll

Great! Didn't know about those. I'll update them, and if everything is OK, I'll move them out of the "workarounds" directory.

If the proposed change is correct, then the example in these tests will be structurized correctly even after removing the "-unify-loop-exits" argument. These tests should also be checked and then updated accordingly.

In fact, it would be nice to update the following tests. But they have a very confusing history and they are specific to AMDGPU. I can volunteer to do this later as a follow up.

  • test/Transforms/StructurizeCFG/AMDGPU/backedge-id-bug.ll
  • test/Transforms/StructurizeCFG/AMDGPU/backedge-id-bug-xfail.ll

I'll try to take a look at those as well. These are "XFAIL" right now. If I'm understanding correctly, You assume they should pass with this patch?

It seems the new commit is an incremental update on the previous commit. Can you please submit a single squashed commit, so that the change can be compared against the original state of the trunk?

I am not sure what do you mean. In this page in the Phabricator, under [Revision Contents]->[History] you can choose which of the patch revisions to diff. The default is "Base" against the latest "Diff no.".

Sorry about that! I can see the diff now ...

Also, the following tests were created specifically to demonstrate the wrong ordering:

  • test/Transforms/StructurizeCFG/workarounds/needs-unified-loop-exits.ll
  • test/Transforms/StructurizeCFG/workarounds/needs-fr-ule.ll

Great! Didn't know about those. I'll update them, and if everything is OK, I'll move them out of the "workarounds" directory.

Note that needs-fr-ule.ll is quite a painful CFG from a real world testcase. It needs FixIrreducible pass, which produces extra loops that have the same ordering problem being fixed here. I'll try to run your patch myself and point out which blocks show up in the wrong order.

If the proposed change is correct, then the example in these tests will be structurized correctly even after removing the "-unify-loop-exits" argument. These tests should also be checked and then updated accordingly.

In fact, it would be nice to update the following tests. But they have a very confusing history and they are specific to AMDGPU. I can volunteer to do this later as a follow up.

  • test/Transforms/StructurizeCFG/AMDGPU/backedge-id-bug.ll
  • test/Transforms/StructurizeCFG/AMDGPU/backedge-id-bug-xfail.ll

I'll try to take a look at those as well. These are "XFAIL" right now. If I'm understanding correctly, You assume they should pass with this patch?

That would be great! But like I said, the file history is confusing, and the last time that I looked, I was unable to decide which blocks are in the wrong order.

llvm/lib/Transforms/Scalar/StructurizeCFG.cpp
326

Wouldn't this be more readable as a simple iteration with a temporary vector? The temporary vector is no more expensive than the stack created for the recursive calls. Essentially:

while (L && L != Ancestor) {
  push back L to temporary vector;
  L = parent;
}

if (!L) return false; // Ancestor is not an ancestor of L

pop temporary vector to WorkList
return true;
341

This pointer is never checked for nullptr before it is used. So either assert that it is not null, or use a reference instead. Also what does the previous line mean? What is the size of a nullptr loop?

370

Isn't this the same as while (!POT.empty())? That would be more readable, and also convey the fact that a node from POT is definitely consumed on each iteration.

385

Why not just have while (CurrentLoopSize) here? At least from what I could see, the value should be non-zero when this point is reached from above, right?

ekatz marked 4 inline comments as done.May 3 2020, 12:57 AM
ekatz added inline comments.
llvm/lib/Transforms/Scalar/StructurizeCFG.cpp
326

I found a nice iterative (non-recursive) way to write this function. I'll change to that.

341

I'll add some comments, and an assert.

370

POT is not always get emptied. If there are no skipped nodes there is no need to erase anything, and we do not enter the following if statement.

385

The problem is that CurrentLoopSize may be null in case all of the nodes have a parent loop (none outside from every loop).
In that case, CurrentLoopSize will only be valid from line 408; and remain that way all the way until the end.

ekatz updated this revision to Diff 261684.May 3 2020, 1:03 AM

Done requested changes.

sameerds added inline comments.May 3 2020, 7:43 AM
llvm/lib/Transforms/Scalar/StructurizeCFG.cpp
385

I am not sure I follow. Can you please rephrase? An example might be useful.

391

This line needs at least two tests: one where CurrentLoop is not an ancestor of L, and another where CurrentLoop is an ancestor of L, but not its immediate parent.

ekatz marked an inline comment as done.May 3 2020, 8:09 AM
ekatz added inline comments.
llvm/lib/Transforms/Scalar/StructurizeCFG.cpp
385

The simplest example is a region without loops. Then the CurrentLoop (and in turn, CurrentLoopSize) is nullptr.

sameerds added inline comments.May 3 2020, 9:15 PM
llvm/lib/Transforms/Scalar/StructurizeCFG.cpp
391

The second case is particularly important, where CurrentLoop is not an immediate parent of L. This situation happens when some node N in CurrentLoop has a successor S which is the header of L. If S was not the header, then L would become an irreducible region and hence not represented as a loop in LLVM. But that contradicts the assumption that CurrentLoop is not the parent of L. If there was a parent P of L (where P is itself a child of CurrentLoop), then P's header does not dominate S, and hence it is also an irreducible region. Again, any such P would not be represented in LLVM. I am not sure if I missed anything here ... if I am right, then the function pushPathToAncestor() would be a whole lot simpler.

ekatz marked an inline comment as done.May 4 2020, 6:13 AM
ekatz added inline comments.
llvm/lib/Transforms/Scalar/StructurizeCFG.cpp
391

That is a great observation!
Thanks!

ekatz updated this revision to Diff 261795.May 4 2020, 6:14 AM

Removed pushPathToAncestor.

The implementation looks okay to me. The new comments help a lot. Waiting for tests mentioned in the comments so far ... one new test (sibling loops where the DFS jumps from one to the other before finishing the first loop) and updates to existing tests.

I have bad news. This fix does not help with the test needs-fr-ule.ll, i.e., the resulting CFG is incorret if I remove the unify-loop-exits pass. The fix-irreducible pass produces an inner loop with the header "irr.guard" and a latch "while.cond47". This is inside an outer loop with header "while.cond" and many backedges. When I run the structurizer with the current fix on this graph, the backedge of the inner loop is lost. Instead we see a spurious loop between two new blocks Flow5 and Flow4, which is governed by incorrect boolean constants when in fact they should have been %Pred variables. That information is lost along the edge Flow8 to Flow5.

Now I am kicking myself for not remembering this earlier. I had encountered the same problem, which is why we introduced the unify-loop-exits pass. Things are not so easy with nested loops. It's not enough to process all inner blocks before outer blocks. The ordering of the outer blocks relative to the inner loop also matters, and there does not seem to be a practical way to preserve it.

ekatz added a comment.May 6 2020, 6:29 AM

The problem with those tests is something else, and not produced by the wrong ordering. You may refer to PR25378 for an example.
Still, this fix the ordering issue and fixes PR41509 (and its duplicates).
I think we should insert this patch, and attend to the other bug as a separate issue.

The problem with those tests is something else, and not produced by the wrong ordering. You may refer to PR25378 for an example.
Still, this fix the ordering issue and fixes PR41509 (and its duplicates).
I think we should insert this patch, and attend to the other bug as a separate issue.

I see. It sure does look like PR25378. Then the only thing left is one new test with two sibling loops. The intention is to cover the case where CurrentLoop is not the parent of L.

ekatz added a comment.May 6 2020, 9:40 AM

I see. It sure does look like PR25378. Then the only thing left is one new test with two sibling loops. The intention is to cover the case where CurrentLoop is not the parent of L.

I don't think it is possible, although I might be wrong.
Because the way I see it, for loop A be interfered with another loop B, while POT, there must be at least one block that belongs to loop A that is dependent on at least one block in loop B. But then loop B must be finished before loop A, and actually inside it. Otherwise, it won't be recognized as a separate loop.

I hope I explained it clear enough. Otherwise, can you please give an example?

I see. It sure does look like PR25378. Then the only thing left is one new test with two sibling loops. The intention is to cover the case where CurrentLoop is not the parent of L.

I don't think it is possible, although I might be wrong.
Because the way I see it, for loop A be interfered with another loop B, while POT, there must be at least one block that belongs to loop A that is dependent on at least one block in loop B. But then loop B must be finished before loop A, and actually inside it. Otherwise, it won't be recognized as a separate loop.

I hope I explained it clear enough. Otherwise, can you please give an example?

I agree. That means that if L exists, then CurrentLoop must be its parent, right? So instead of checking for this condition, the code will have to assert it.

I see. It sure does look like PR25378. Then the only thing left is one new test with two sibling loops. The intention is to cover the case where CurrentLoop is not the parent of L.

I don't think it is possible, although I might be wrong.
Because the way I see it, for loop A be interfered with another loop B, while POT, there must be at least one block that belongs to loop A that is dependent on at least one block in loop B. But then loop B must be finished before loop A, and actually inside it. Otherwise, it won't be recognized as a separate loop.

I hope I explained it clear enough. Otherwise, can you please give an example?

I agree. That means that if L exists, then CurrentLoop must be its parent, right? So instead of checking for this condition, the code will have to assert it.

Actually, I take that back. Consider the following CFG:

A -> H1, H2
H1->B, C
B->H1
C->B, H2
H2->E
E->H2, F

A is the entry and F is the exit. The loops (H1, B, C) and (H2, E) are siblings with backedges B->H1 and E->H2 respectively. C is an exiting block in the first loop that branches to H2. One POT would be "B, F, E, H2, C, H1, A". When iterating in reverse, the loop with H1 is started first, but H2 from the second loop is encountered before the first loop is completed.

ekatz added a comment.May 7 2020, 4:14 AM

Actually, I take that back. Consider the following CFG:

A -> H1, H2
H1->B, C
B->H1
C->B, H2
H2->E
E->H2, F

A is the entry and F is the exit. The loops (H1, B, C) and (H2, E) are siblings with backedges B->H1 and E->H2 respectively. C is an exiting block in the first loop that branches to H2. One POT would be "B, F, E, H2, C, H1, A". When iterating in reverse, the loop with H1 is started first, but H2 from the second loop is encountered before the first loop is completed.

In this case, those loops are in different regions, and as this is a region pass, they won't appear in the same POT.

sameerds added a comment.EditedMay 7 2020, 5:23 AM

Actually, I take that back. Consider the following CFG:

A -> H1, H2
H1->B, C
B->H1
C->B, H2
H2->E
E->H2, F

A is the entry and F is the exit. The loops (H1, B, C) and (H2, E) are siblings with backedges B->H1 and E->H2 respectively. C is an exiting block in the first loop that branches to H2. One POT would be "B, F, E, H2, C, H1, A". When iterating in reverse, the loop with H1 is started first, but H2 from the second loop is encountered before the first loop is completed.

In this case, those loops are in different regions, and as this is a region pass, they won't appear in the same POT.

You need to look at the larger pattern than just the specific example. It should be possible to add exits in both loops that force them into the same region. For example, H2->F and B->F.

ekatz updated this revision to Diff 263067.May 10 2020, 4:34 AM

Added test for interleaved sibling loops, and change the test file name to "interleaved-loop-order.ll".

sameerds accepted this revision.EditedMay 10 2020, 7:30 PM

LGTM. It would be nice if you can move the following tests to the new file and demonstrate that that they are fixed. But that need not hold up this review any longer.

  • test/Transforms/StructurizeCFG/AMDGPU/backedge-id-bug.ll
  • test/Transforms/StructurizeCFG/AMDGPU/backedge-id-bug-xfail.ll

[EDIT] Actually added the test names.

This revision is now accepted and ready to land.May 10 2020, 7:30 PM

LGTM. It would be nice if you can move the following tests to the new file and demonstrate that that they are fixed. But that need not hold up this review any longer.

  • test/Transforms/StructurizeCFG/AMDGPU/backedge-id-bug.ll
  • test/Transforms/StructurizeCFG/AMDGPU/backedge-id-bug-xfail.ll

[EDIT] Actually added the test names.

The CFG produced by "backedge-id-bug.ll" seems to be OK, just a little different than expected due to a different ordering scheme used when it was produced. This needs to be double checked, but as I see it, the expected just needs to be updated.
In "backedge-id-bug-xfail.ll" the "verify-region-info" fails, which is a separate bug.

In any case, those files produce the same CFG with and without this patch.
So, as you have already stated, they are out of the scope of this patch.

This revision was automatically updated to reflect the committed changes.
piotr added a subscriber: piotr.May 14 2020, 3:25 AM

Hi, This commit makes some compilations go into an infinite loop. I will try to prepare a reproducer.

ekatz added a comment.May 14 2020, 5:30 AM

Hi, This commit makes some compilations go into an infinite loop. I will try to prepare a reproducer.

Thanks.
Waiting for a test case...

piotr added a comment.May 14 2020, 6:43 AM

- this can possibly be simplified even more, but it takes ages to cut down.

foad added a comment.May 14 2020, 7:24 AM

this can possibly be simplified even more, but it takes ages to cut down.

Here's a reduced case:


Run it with "opt -structurizecfg reduced.ll".

ekatz reopened this revision.May 14 2020, 7:48 AM

Thanks both of you.
I'll revert and update the patch for it.

This revision is now accepted and ready to land.May 14 2020, 7:48 AM
ekatz updated this revision to Diff 264018.May 14 2020, 9:51 AM

Fixed the bug causing an infinite loop in the new testcase.

ekatz requested review of this revision.May 14 2020, 9:53 AM
ekatz added reviewers: piotr, foad.

TBH, I spent a small amount of time visualizing the new testcase, but couldn't figure out the problem, nor the solution. I see that the check for currentLoopSize is moved up the control flow a bit, but I didn't see why. A few LLVM_DEBUG lines would be very helpful to trace what the structurizer is doing. Also, can the new test case be reduced further? There are a couple of simple chains, and a few branches. Are all of them necessary? The main problem seems to be an inner loop with just two blocks where both blocks exit. I don't know whether both these properties matter. For example, will the problem happen with a single block that loops on itself?

ekatz updated this revision to Diff 265551.May 21 2020, 11:14 AM

Simplified the new test case.

ekatz added a comment.EditedMay 21 2020, 11:17 AM

TBH, I spent a small amount of time visualizing the new testcase, but couldn't figure out the problem, nor the solution. I see that the check for currentLoopSize is moved up the control flow a bit, but I didn't see why.

We need to move up the check for *CurrentLoopSize, as in the case it is zero, then we no longer need to skip nodes, and just continue to the next node/loop.
The simplified, new test case, demonstrate a case where it is needed.

TBH, I spent a small amount of time visualizing the new testcase, but couldn't figure out the problem, nor the solution. I see that the check for currentLoopSize is moved up the control flow a bit, but I didn't see why.

We need to move up the check for *CurrentLoopSize, as in the case it is zero, then we no longer need to skip nodes, and just continue to the next node/loop.
The simplified, new test case, demonstrate a case where it is needed.

Actually, I spent some more time looking at this today. In the simplified testcase, the region being processed is { D, E, C } listed in post-order. Each one of these is a child of some loop, although the loop for D is not in the current region. As a result, when the outermost loop starts, CurrentLoop is nullptr but CurrentLoopSize is zero because there is no node in the null loop. Checking for CurrentLoopSize is not a very satisfying explanation for the fix, since the loop for D actually has an entry in LoopSizes.

In other words, the pre-condition is that the sum of LoopSizes is the number of nodes in the region. Then the post-condition should be that the same sum should be zero. An assertion for this would fail in this testcase.

ekatz updated this revision to Diff 266163.May 26 2020, 4:15 AM

This is a second take of the solution, using SCCs instead of LoopInfo.
This algorithm can also handle irreducible loops, and, generally, any kind of cycle in the graph.

This is a second take of the solution, using SCCs instead of LoopInfo.
This algorithm can also handle irreducible loops, and, generally, any kind of cycle in the graph.

Thanks! This looks really nice now! The sad part is that the structurizer itself is undefined in the presence of irreducible regions, so we will never actually see this in action around irreducible regions.

llvm/lib/Transforms/Scalar/StructurizeCFG.cpp
326

I might be missing something here. OB marks the beginning of the range that originally contained the nodes that are now listed in Nodes. We are now over-writing this range from the beginning with members of each SCC found by the scc_iterator. Where do the rest of the members in Nodes go? Do they have to be rewritten in the remaining range [OB, OE)?

362

Do we really need this for loop? If I understand correctly, orderSCC() works on a subgraph that is guranteed to not be an SCC. Can the same function just take whole region as the starting point?

ekatz marked 2 inline comments as done.May 26 2020, 1:14 PM
ekatz added inline comments.
llvm/lib/Transforms/Scalar/StructurizeCFG.cpp
326

The nodes in Nodes are already connected in the subgraph starting from the Entry node. They are all reachable, as they were in the original SCC that contained Entry and Nodes.
We rewrite the SCC into [OB, OE), which should be the same size as the number of nodes in all the sub-SCCs found in the current for loop.

362

orderSCC does something pretty close, but works on SubGraphNodes, while this function works on RegionNodes. There is no need to create a subgraph if there is no SCC larger than 2, to work on.

sameerds added inline comments.May 27 2020, 10:14 PM
llvm/lib/Transforms/Scalar/StructurizeCFG.cpp
362

This seems like a good use-case for the WrappedSuccIterator class introduced in LoopIterator.h ... that version limits the graph to the body of a loop, but you could build a similar iterator_adapter that limits the graph to a given set of nodes. With such a succ iterator, you could just eliminate the SubGraphNode class itself and let orderSCC do all the work on the entire input region. That will unify the two for-loops which are actually identical in their intention.

ekatz marked an inline comment as done.May 28 2020, 3:41 AM
ekatz added inline comments.
llvm/lib/Transforms/Scalar/StructurizeCFG.cpp
362

I thought about that, but the problem is with the scc_iterator - It created new succ_iterator for each child node, using the GraphTraits given. This means that the NodeRef, on which succ_iterator is created upon, needs to have a reference to the set of valid Nodes (as actual successors).
In any case, we need to create new Nodes.

In addition, please take a look at the implementation of the RNSuccIterator, in which you will find that iterating upon it is a pretty heavy operation, including a map search for every node in getISucc (this calls to getNode(BB) which in turn searches an internal map).

Considering that we create new nodes, and might need to do all over again for each sub SCC, it is better to just rebuild the SCC (with the SubGraphNodes), instead of a simple wrapper (that will requery the map for the appropriate RegionNode of a BasicBlock each time.

sameerds added inline comments.May 29 2020, 10:47 PM
llvm/lib/Transforms/Scalar/StructurizeCFG.cpp
362

I didn't follow the part about creating new nodes. The LoopIterator defines the NodeRef as a tuple <BasicBlock, Loop>, so that it carries the Loop around for use in succ_iterator. What I had in mind was a similar tuple <RegionNode, ValidNodes> where the second part is a collection of nodes that constrain the succ_iterator.

I would choose a more compact implementation here instead of looking into the cost of RNSuccIterator. Avoiding the map does not look like sufficient reason to break away from the idiomatic use of GraphTraits and scc_iterator. I know this looks expensive, but how expensive? Something like O(N * scc_depth)? It is certainly dwarfed by the heavy lifting that the rest of the structurizer is doing.

To clarify, I am not insisting that a new iterator is ideal, but it would certainly be my choice unless there is peformance data asking for improvement.

ekatz marked an inline comment as done.May 29 2020, 11:22 PM
ekatz added inline comments.
llvm/lib/Transforms/Scalar/StructurizeCFG.cpp
362

I didn't like the concept of carrying a reference to the "set" on each node, which will increase the memory size required by the SCC iterator by a factor of 2, and also require requery of possibly many nodes (in the "set").

However, I agree that the performance impact overall is probably negligible, and we can always get back to a more optimized implementation if needed. But for now, the simpler approach is probably the correct one.

I will upload a version without building a subgraph, as suggested.

ekatz updated this revision to Diff 267465.May 30 2020, 10:50 AM

Update algorithm to use a custom GraphTraits and a NodeRef that holds the set of nodes for the subgraph.
This simplifies the algorithm quite a bit.

sameerds accepted this revision.May 31 2020, 9:35 PM

This looks really good. Thanks a lot for bringing it this far! Please do check the #include for LoopInfo.h before submitting.

llvm/lib/Transforms/Scalar/StructurizeCFG.cpp
17

I think this can be removed too. At least a quick grep did not show any use of Loop or LoopInfo other than the code that gets eleminated with this patch.

This revision is now accepted and ready to land.May 31 2020, 9:35 PM
ekatz marked an inline comment as done.Jun 1 2020, 12:44 AM
ekatz added inline comments.
llvm/lib/Transforms/Scalar/StructurizeCFG.cpp
17

You are right. Will do.

foad added inline comments.Jun 1 2020, 1:45 AM
llvm/lib/Transforms/Scalar/StructurizeCFG.cpp
326–328

It's normally called a "topological" sort.

This revision was automatically updated to reflect the committed changes.
ekatz marked an inline comment as done.Jun 1 2020, 3:17 AM
ekatz added inline comments.
llvm/lib/Transforms/Scalar/StructurizeCFG.cpp
326–328

Will fix the comment. Thanks.