Skip to content

[NFC] Add a reverse-postorder queue utility and use it in ConstraintAnalysis flow - #9149

Merged
kripken merged 7 commits into
WebAssembly:mainfrom
kripken:rpo
Sep 24, 2026
Merged

kripken merged 7 commits into
WebAssembly:mainfrom
kripken:rpo

Conversation

@kripken

@kripken kripken commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

This is a lot more efficient in the case of large functions: processing in
reverse postorder means we finish up an if/loop before we look onwards.
The UniqueDeferredQueue tries to do this automatically in a cheap
way: when we see a thing twice, we defer it, so it is processed after the
children - but there is no guarantee it works out, depending on how we
traverse the children. The new utility does this deterministically.

This makes ConstraintAnalysis 2-3x faster on a few large testcases I
am looking at. I see smaller wins elsewhere, and I have not found a
single case where this is slower. Even though we now use a priority
queue (logN push/pop), this approach stores the block index and whether
it is in the queue already in the block itself, which is more efficient than
before.

Helps #4165

@kripken
kripken requested a review from a team as a code owner September 23, 2026 17:57
@kripken
kripken requested review from stevenfontanella and tlively and removed request for a team and stevenfontanella September 23, 2026 17:57

@tlively tlively left a comment

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.

Sounds like you might be able to make this even faster by using Tarjan's algorithm to get the topologically sorted strongly connected components of the CFG. This is O(n) to get the sorted SCCs, then you can simply loop over the SCCs and drive each one to a fixed point without ever revisiting a previous SCC. See src/support/strongly_connected_components.h.

@kripken

kripken commented Sep 23, 2026

Copy link
Copy Markdown
Member Author

Interesting, but let's leave that as a possible followup?

@tlively

tlively commented Sep 23, 2026

Copy link
Copy Markdown
Member

It would be nice to avoid adding this new utility just to rip it out again, but I can review this first if it would significantly slow you down otherwise.

@kripken

kripken commented Sep 23, 2026

Copy link
Copy Markdown
Member Author

Ok, fair enough, I thought about this over lunch. I don't think SCCs help us here:

while (1) {
  if (x) {
    a();
  } else {
    b();
  }
  .. tons of code ..
}

All this is one big SCC, but the order inside it matters a lot: we want to process a(), b() before the tons of code after them. That is, what actually matters here is the flow of control, which we want to follow - and RPO does just that. The If isn't an SCC, but the If must be processed first, in other words.

We could perhaps adapt SCC to this, but we'd need to recursively analyze inside SCCs - in this loop example, we'd need to differentiate the If from the stuff after it etc. That will already not be linear. (But even if we did this recursion, we still need RPO order to get the If to happen first - again, it is not an SCC even modulo the loop.)

@tlively

tlively commented Sep 23, 2026

Copy link
Copy Markdown
Member

Hmm, good point. But if there are multiple backedges to the beginning of the loop (e.g. multiple continues), then the priority queue will actually be a pessimization because new information propagated from a single backedge will then be propagated all the way through the loop's graph before another piece of information from another backedge is propagated back to the loop header.

The best of both worlds would be to recursively find SCCs (i.e. loops) and recursively do a reverse post-order traversal of the blocks and nested loops within each loop. The standard terms for this seem to be "Loop Nesting Forest" and "Hierarchical Topological Sorting." The state of the art seems to be described here: https://maskray.me/blog/irreducible-loops.

However, we can assume that our CFG is reducible, so we could more simply use a dominator tree to identify all the loop headers and find the nested loops from there, or we could use Bourndoncle's Algorithm to compute the weak topological order (WTO), i.e. the topological order, ignoring backedges, with bracketing around the nested loops, in near-linear time.

... but for now I'll go ahead and review this PR.

@kripken

kripken commented Sep 23, 2026

Copy link
Copy Markdown
Member Author

Exactly, some kind of nested loop analysis seems right. Could be worth looking at. Actually the relooper does the same thing, I realize (though not sure if we can share the code).

Anyhow, this PR is very short and brings a huge win for now! 😄

Comment thread src/cfg/rpo.h Outdated
Comment on lines +21 to +22
#ifndef rpo_h
#define rpo_h

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.

Suggested change
#ifndef rpo_h
#define rpo_h
#ifndef cfg_rpo_h
#define cfg_rpo_h

@kripken
kripken merged commit 296af31 into WebAssembly:main Sep 24, 2026
16 checks passed
kripken added a commit that referenced this pull request Sep 24, 2026
This makes CoalesceLocals 15% faster, for similar reasons as #9149.
kripken added a commit that referenced this pull request Sep 24, 2026
This makes the pass 25% faster, for similar reasons as #9149.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants