Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
dad6624
Add a dominator-tree WTO utility for reducible CFGs
tlively Oct 2, 2026
eb5b060
Use WTOWorklist in ConstraintAnalysis and RedundantSetElimination
tlively Oct 2, 2026
d1eb897
Use BasicBlock::contents.index in DomTree
tlively Oct 2, 2026
383c406
Collapse inner loops with union-find during WTO construction
tlively Oct 2, 2026
48737e0
Fast-path loop-free CFGs in WTOWorklist
tlively Oct 6, 2026
2ec3749
Flatten WeakTopologicalOrdering into a single entry vector
tlively Oct 6, 2026
50f7064
Work around clang++-18 crash on defaulted WTOCycle::operator==
tlively Oct 6, 2026
60f33d0
Merge branch 'domtree-wto2' into wto-passes
tlively Oct 6, 2026
4850b4c
Merge branch 'wto-passes' into domtree-block-indices
tlively Oct 6, 2026
ec7e4ab
Merge branch 'domtree-block-indices' into wto-union-find
tlively Oct 6, 2026
d899204
Merge branch 'wto-union-find' into wto-fast-paths
tlively Oct 6, 2026
5dfa650
Merge branch 'wto-fast-paths' into wto-flat
tlively Oct 6, 2026
08750e7
tighten up definition
tlively Oct 6, 2026
4aea7cd
Moar comments (I wrote them myself!)
tlively Oct 6, 2026
a469c96
mini CFG comment
tlively Oct 6, 2026
f4d0e76
recursion comments
tlively Oct 7, 2026
f1a4061
Work around GCC 11 ICE on local static constexpr in WTO
tlively Oct 7, 2026
bece156
Merge branch 'domtree-wto2' into wto-passes
tlively Oct 7, 2026
865e017
Merge branch 'wto-passes' into domtree-block-indices
tlively Oct 7, 2026
268e04d
Merge branch 'domtree-block-indices' into wto-union-find
tlively Oct 7, 2026
1b7906b
Merge branch 'wto-union-find' into wto-fast-paths
tlively Oct 7, 2026
51c3d33
Merge branch 'wto-fast-paths' into wto-flat
tlively Oct 7, 2026
9fb7800
Merge branch 'main' into wto-passes
tlively Oct 7, 2026
2f54272
Merge branch 'wto-passes' into domtree-block-indices
tlively Oct 7, 2026
25ec057
Merge branch 'domtree-block-indices' into wto-union-find
tlively Oct 7, 2026
e4539b6
Merge branch 'wto-union-find' into wto-fast-paths
tlively Oct 7, 2026
4009e59
Merge branch 'wto-fast-paths' into wto-flat
tlively Oct 7, 2026
8964e96
"for domtree"
tlively Oct 7, 2026
91d6872
setBlockIndices
tlively Oct 7, 2026
5001f17
Merge branch 'main' into wto-passes
tlively Oct 7, 2026
4b1947a
Merge branch 'wto-passes' into domtree-block-indices
tlively Oct 7, 2026
bc37c1f
Merge branch 'domtree-block-indices' into wto-union-find
tlively Oct 7, 2026
ae09421
Merge branch 'wto-union-find' into wto-fast-paths
tlively Oct 7, 2026
5988797
Merge branch 'wto-fast-paths' into wto-flat
tlively Oct 7, 2026
db7d4fe
Merge branch 'main' into domtree-block-indices
tlively Oct 9, 2026
939de33
Merge branch 'domtree-block-indices' into wto-union-find
tlively Oct 9, 2026
e5d29c5
Merge branch 'wto-union-find' into wto-fast-paths
tlively Oct 9, 2026
857c917
Merge branch 'wto-fast-paths' into wto-flat
tlively Oct 9, 2026
7b7eeb7
Have DomTree set block indices itself
tlively Oct 9, 2026
a2089ff
Merge branch 'domtree-block-indices' into wto-union-find
tlively Oct 9, 2026
252add9
Merge branch 'wto-union-find' into wto-fast-paths
tlively Oct 9, 2026
c18519e
Check loopTops.empty() instead of hasBackEdge()
tlively Oct 9, 2026
59bdbd4
Merge branch 'wto-fast-paths' into wto-flat
tlively Oct 9, 2026
a3bbfbc
more comments on ufParent
tlively Oct 9, 2026
7513840
Merge branch 'wto-union-find' into wto-fast-paths
tlively Oct 9, 2026
9f2a310
Merge branch 'wto-fast-paths' into wto-flat
tlively Oct 9, 2026
5acacd3
Merge branch 'wto-union-find' into wto-fast-paths
tlively Oct 9, 2026
8220e47
Merge branch 'wto-fast-paths' into wto-flat
tlively Oct 9, 2026
3f4236a
Remove redundant loop-free WTOWorklist test
tlively Oct 9, 2026
0ad28a3
Merge branch 'wto-fast-paths' into wto-flat
tlively Oct 9, 2026
a2f9bbb
expand comment on entry
tlively Oct 9, 2026
73ffa92
Merge remote-tracking branch 'origin/main' into wto-fast-paths
tlively Oct 9, 2026
8851991
Merge branch 'wto-fast-paths' into wto-flat
tlively Oct 9, 2026
4d5d092
fix build
tlively Oct 9, 2026
4411846
Merge remote-tracking branch 'origin/main' into wto-flat
tlively Oct 9, 2026
6c85f9f
clarify what indices we are talking about
tlively Oct 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 45 additions & 46 deletions src/cfg/wto.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@

#include <cassert>
#include <memory>
#include <variant>
#include <vector>

#include "cfg/domtree.h"
Expand All @@ -94,18 +93,25 @@ namespace wasm {
template<typename BasicBlock> struct WeakTopologicalOrdering {
static constexpr Index NoIndex = Index(-1);

struct Cycle;
using Element = std::variant<BasicBlock*, Cycle>;
using List = std::vector<Element>;

struct Cycle {
List elems;

BasicBlock* head() const { return std::get<BasicBlock*>(elems.front()); }
bool operator==(const Cycle& other) const { return elems == other.elems; }
// `entries` is the flattened sequence of blocks in weak topological order,
// plus markers for the end of cycles.
//
// For normal block entries:
// - cycleTarget == NoIndex
// - block is the corresponding basic block
//
// For end of cycle entries:
// - cycleTarget is the index in `entries` of the head of the cycle, i.e.
// where we will go next if we need to process the cycle again.
// - block is the basic block at the head of the cycle, used to check whether
// we need to process the cycle again (by checking contents.inQueue).
//
struct Entry {
BasicBlock* block;
Index cycleTarget;
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What marks the cycle's start? Maybe I'm not understanding this comment. Is cycleTarget meaningful in all cases?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Nothing marks the cycle's start besides the fact that later cycleTargets point back to it. cycleTarget determines whether this is a "normal" entry (when it is NoTarget) or a cycle end marker entry.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm still not sure how to read this. So a particular block B will appear twice, once at the start and once at the end?

Some things that might be confusing me: the word "visits" on line 97, and the word "either".

Perhaps this can be explained as follows?

1. Each block appears in one Entry.}
2. cycleTarget is usually NoTarget, except for the end of a cycle, where it points to the cycle's start.

For example:

[ {B1, NoTarget}, {B2, B1} ]

This is a simple loop `B1 -> B2 -> B1`.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not quite. When cycleTarget != NoTarget, block is the header of the loop, which already appeared previously as a normal entry (see lines 247 and 249). Storing the loop header directly with the cycle end marker means line 332 has one less indirection.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ok, then how about this comment:

1. A normal Entry refers to a block, and has NoTarget for cycleTarget.
2. A loop end is marked by an Entry where the block (which appeared before
   already) is the header, and cycleTarget is...?

For example:
[..]

Where I still do not follow is the end of part 2. It seems like the block is already saying where the backedge goes to, so we just need a boolean "this is a cycle end"? What information is conveyed in cycleTarget?

(an example in the comment might help)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it make sense to add a comment explaining what the WTO index is? The large toplevel comment only mentions the RPO index, and I don't see anything else below.

Or are you saying this is all quite obvious? Sorry if that is the case. Reading the code I see

        Index startPc = entries.size();
        entries.push_back({block, NoIndex});
        self(self, nodes[curr].firstChild);
        entries.push_back({block, startPc});

That seems to be where the "WTO Index" is generated, but I don't see it called that, and it's not clear to me how it differs from the RPO index. That is, isn't there one entry per basic block? Or is the fact that a basic block can appear a second time (to close the loop) the reason for the discrepancy in indexing? If so, that is exactly the kind of explanation I am looking for.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I've been using "WTO index" as shorthand for "index in the entries vector" to clarify its distinction from "RPO index." The term "WTO index" does not appear in the code, so I think it should not need explaining.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Or is the fact that a basic block can appear a second time (to close the loop) the reason for the discrepancy in indexing? If so, that is exactly the kind of explanation I am looking for.

Yes, that's right. I can clarify that in the code.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Also there's the whole part where we do the backward search in the domtree and build linked lists of blocks in each loop. It's not clear to me that the flattened block order that produces matches the original RPO order, but maybe it does.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was thinking that the order ensures it is the RPO order, so I didn't even consider there were two indexes here...


List elems;
std::vector<Entry> entries;

WeakTopologicalOrdering(std::vector<std::unique_ptr<BasicBlock>>& blocks);
};
Expand Down Expand Up @@ -239,27 +245,26 @@ WeakTopologicalOrdering<BasicBlock>::WeakTopologicalOrdering(
}
}

// Traverse the linked lists of children, materializing them as WTO elements.
// Traverse the linked lists of children, materializing them as WTO entries.
// Loop depth should be limited, so doing this recursively should be fine. If
// it ever causes an issue, we can un-recurse this.
// TODO: Flatten the WTO into a single contiguous vector of entries with cycle
// jump targets to avoid per-cycle vector allocations and recursion.
auto buildList = [&](auto& self, Index firstChild, List& out) -> void {
entries.reserve(numBlocks * 2);
auto emitList = [&](auto& self, Index firstChild) -> void {
for (Index curr = firstChild; curr != NoIndex;
curr = nodes[curr].nextSibling) {
auto* block = blocks[curr].get();
if (nodes[curr].isLoopHeader) {
Cycle cycle;
cycle.elems.emplace_back(block);
self(self, nodes[curr].firstChild, cycle.elems);
out.emplace_back(std::move(cycle));
Index startPc = entries.size();
entries.push_back({block, NoIndex});
self(self, nodes[curr].firstChild);
entries.push_back({block, startPc});
} else {
out.emplace_back(block);
entries.push_back({block, NoIndex});
}
}
};

buildList(buildList, topFirstChild, elems);
emitList(emitList, topFirstChild);
}

// Given a CFG in reverse postorder (e.g. from cfg-traversal), run a forward
Expand Down Expand Up @@ -304,34 +309,28 @@ template<typename CFG> struct WTOWorklist {
}
return;
}
// Iterate through each element in the current cycle's list (or the
// top-level list), which will be in reverse postorder. Visit those that are
// in the queue, which may push later elements to the queue. When there is a
// nested cycle, repeatedly visit it recursively until it stabilizes before
// continuing on. We could un-recurse this, but the loop depth is expected
// to be acceptably small.
// Iterate through the flattened WTO entries in reverse postorder. Visit
// blocks that are in the queue, which may push later blocks or loop headers
// to the queue. At the end of a cycle, jump back to the cycle header entry
// if the header was re-queued so the cycle repeats until it stabilizes.
WeakTopologicalOrdering<BasicBlock> wto(cfg.basicBlocks);
auto evalList =
[&](auto& self,
const typename WeakTopologicalOrdering<BasicBlock>::List& list)
-> void {
for (const auto& elem : list) {
if (auto* block = std::get_if<BasicBlock*>(&elem)) {
if ((*block)->contents.inQueue) {
(*block)->contents.inQueue = false;
visit(*block);
}
} else {
const auto& cycle =
std::get<typename WeakTopologicalOrdering<BasicBlock>::Cycle>(elem);
BasicBlock* head = cycle.head();
do {
self(self, cycle.elems);
} while (head->contents.inQueue);
const auto& entries = wto.entries;
Index pc = 0;
Index end = entries.size();
while (pc < end) {
const auto& entry = entries[pc];
if (entry.cycleTarget == WeakTopologicalOrdering<BasicBlock>::NoIndex) {
if (entry.block->contents.inQueue) {
entry.block->contents.inQueue = false;
visit(entry.block);
}
++pc;
} else if (entry.block->contents.inQueue) {
pc = entry.cycleTarget;
} else {
++pc;
}
};
evalList(evalList, wto.elems);
}
}
};

Expand Down
35 changes: 24 additions & 11 deletions test/gtest/wto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,18 +135,31 @@ std::ostream& operator<<(std::ostream& os, const WTOList& list) {

using BasicBlock = TestCFG::BasicBlock;

WTOList toIndexWTO(const WeakTopologicalOrdering<BasicBlock>::List& src) {
WTOList dst;
for (const auto& elem : src) {
if (auto* b = std::get_if<BasicBlock*>(&elem)) {
dst.emplace_back((*b)->contents.index);
WTOList toIndexWTO(const WeakTopologicalOrdering<BasicBlock>& wto) {
constexpr Index NoIndex = WeakTopologicalOrdering<BasicBlock>::NoIndex;
std::vector<std::pair<Index, WTOElem>> stack;
for (Index pc = 0; pc < wto.entries.size(); ++pc) {
const auto& entry = wto.entries[pc];
if (entry.cycleTarget == NoIndex) {
stack.emplace_back(pc, WTOElem(entry.block->contents.index));
} else {
const auto& cycle =
std::get<WeakTopologicalOrdering<BasicBlock>::Cycle>(elem);
EXPECT_EQ(cycle.head(), std::get<BasicBlock*>(cycle.elems.front()));
dst.emplace_back(WTOCycle(toIndexWTO(cycle.elems)));
EXPECT_EQ(entry.block, wto.entries[entry.cycleTarget].block);
size_t start = stack.size();
while (stack[start - 1].first != entry.cycleTarget) {
--start;
}
WTOList cycleElems;
for (size_t i = start - 1; i < stack.size(); ++i) {
cycleElems.push_back(std::move(stack[i].second));
}
stack.erase(stack.begin() + start, stack.end());
stack.back() = {NoIndex, WTOElem(WTOCycle(std::move(cycleElems)))};
}
}
WTOList dst;
for (auto& [_, elem] : stack) {
dst.push_back(std::move(elem));
}
return dst;
}

Expand Down Expand Up @@ -209,8 +222,8 @@ void verifyWTOInvariants(const TestCFG& cfg, const WTOList& wto) {

WTOList getWTO(TestCFG& cfg) {
WeakTopologicalOrdering<BasicBlock> wto(cfg.basicBlocks);
EXPECT_EQ(wto.elems, wto.elems);
auto list = toIndexWTO(wto.elems);
auto list = toIndexWTO(wto);
EXPECT_EQ(list, list);
verifyWTOInvariants(cfg, list);
return list;
}
Expand Down
Loading