Skip to content

Collapse inner loops with union-find during WTO construction - #9218

Merged
tlively merged 8 commits into
domtree-block-indicesfrom
wto-union-find
Oct 9, 2026
Merged

tlively merged 8 commits into
domtree-block-indicesfrom
wto-union-find

Conversation

@tlively

@tlively tlively commented Oct 6, 2026

Copy link
Copy Markdown
Member

During reverse-RPO natural loop discovery in WeakTopologicalOrdering,
collapse each discovered loop body into its header using union-find with
path compression, and skip over already-collapsed inner loops when
walking immediate dominators in dominates(). This prevents outer loops
from re-traversing inner loop bodies, bounding natural loop discovery to
O(E alpha(N)) instead of O(N * depth) on deeply nested loops.

Benchmark results across 16 WebAssembly modules (3 iterations,
interleaved):

  • --constraint-analysis:
    • Geomean: 1.576s -> 1.564s (-0.7%)
    • Total time: 62.82s -> 62.40s (-0.7%)
  • --rse:
    • Geomean: 0.859s -> 0.853s (-0.7%)
    • Total time: 25.40s -> 25.06s (-1.3%)

During reverse-RPO natural loop discovery in WeakTopologicalOrdering,
collapse each discovered loop body into its header using union-find with
path compression, and skip over already-collapsed inner loops when
walking immediate dominators in dominates(). This prevents outer loops
from re-traversing inner loop bodies, bounding natural loop discovery to
O(E alpha(N)) instead of O(N * depth) on deeply nested loops.

Benchmark results across 16 WebAssembly modules (3 iterations,
interleaved):
- --constraint-analysis:
  - Geomean: 1.576s -> 1.564s (-0.7%)
  - Total time: 62.82s -> 62.40s (-0.7%)
- --rse:
  - Geomean: 0.859s -> 0.853s (-0.7%)
  - Total time: 25.40s -> 25.06s (-1.3%)
@tlively
tlively requested a review from a team as a code owner October 6, 2026 07:10
@tlively
tlively requested review from kripken and removed request for a team October 6, 2026 07:10
(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)
(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)
(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)
(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)(loop)

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.

If this PR speeds up this kind of mega-loop that suggests we care about such things. But didn't we say we don't care about mega-loops when we said recursion was fine?

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'd say there are two kinds of speedup we care about: 1) wall clock speedup on real-world code, and 2) algorithmic complexity improvements to guard against pathological cases, which may occur rarely in real code or in e.g. fuzzer-generated code.

This test demonstrates the algorithmic complexity improvement. It takes over 5 seconds to run before this change.

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 lean towards this not mattering, though. 5 seconds isn't that bad, and this is extremely unrealistic code?

Adding a little complexity for things like this can lead to a lot of accumulated complexity for no practical benefit, I worry.

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.

This is a dramatic improvement in the time complexity of the algorithm, though. Wasn't that one of your concerns going into this?

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.

Yes, but to the extent it is realistic? Hundreds of nested loops is not realistic, and 5 seconds is really not that bad for such an unrealistic case.

But do you feel strongly here? I lean against it, but I don't want to block something you consider worthwhile.

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'm getting more robust benchmarks for just this change to inform a decision.

@tlively tlively Oct 8, 2026 •

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.

Ok, the performance differences due to this change are largely in the noise, except for these statistically significant runtime improvements:

  • clang.wasm --rse: -8.6%
  • KotlinConfApp-pre-wasm-opt.wasm --constraint-analysis: -5.2%
  • esbuild.wasm --rse: -1.2%

The clang improvement in particular makes me think that this is worth landing, even setting aside the algorithmic improvements.

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.

Fair enough, happy to review and move forward

Comment thread src/cfg/wto.h
// The index of the loop header we last traversed this node for, used
// instead of a `visited` set during the DFS.
Index lastVisitedBy = NoIndex;
// The parent in the union-find forest used to collapse inner loops into

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.

Does union-find mean disjoint sets? If so, where is that data structure, since I don't seem to see this PR add one? And what is a "forest" of such things?

I feel like there is a bunch of concepts here without a large explanatory comment, or have I missed the forest for the trees 😆

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.

Yes, "union-find forest" and "disjoint sets" are two names for the same abstract data structure and algorithm. We have support/disjoint_sets.h, but we don't use it here because it is simpler and more efficient to just rearrange the existing references between entries into the union-find structure.

I can add a comment redirecting the reader to support/disjoint_sets.h to read about the algorithm, if you think that would help? Although that just links to wikipedia, so maybe better to link there directly: https://en.wikipedia.org/wiki/Disjoint-set_data_structure.

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 am more looking for a comment saying where the disjoint-set implementation is here. This doesn't add new data structures, so seemingly it was here all along, and this PR just "reinterprets" something existing? I'm not sure where to look for that.

@tlively tlively Oct 8, 2026 •

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.

Well the only structural element in the data structure is each item's parent pointer. An item's parent pointer is unset if the item is the representative element of its set, and otherwise there is a path of parent pointers leading to the representative element for the set. So this ufParent is the entire data structure.

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.

Thanks! This is what I was looking for in a comment.

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.

Cool, I'll update the code comment to say something like that.

@kripken kripken 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.

Ok, now it seems pretty clear, lgtm (edit: with that comment)

@tlively
tlively merged commit 1ff7152 into domtree-block-indices Oct 9, 2026
16 checks passed
@tlively
tlively deleted the wto-union-find branch October 9, 2026 04:44
@tlively
tlively restored the wto-union-find branch October 9, 2026 04:45
@tlively

tlively commented Oct 9, 2026

Copy link
Copy Markdown
Member Author

Darn, I accidentally merged this into the previous PR instead of into main :(

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