Skip to content

Flatten WeakTopologicalOrdering into a single entry vector - #9220

Open
tlively wants to merge 5 commits into
wto-fast-pathsfrom
wto-flat
Open

tlively wants to merge 5 commits into
wto-fast-pathsfrom
wto-flat

Conversation

@tlively

@tlively tlively commented Oct 6, 2026

Copy link
Copy Markdown
Member

Replace the recursive std::variant/Cycle representation of
WeakTopologicalOrdering with a single contiguous std::vector
where cycle ends store the header block and jump target index. This
avoids per-cycle vector allocations and recursive evaluation in
WTOWorklist::run.

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

  • --constraint-analysis:
    • Geomean: 1.606s -> 1.592s (-0.9%)
    • Total time: 64.05s -> 63.67s (-0.6%; faster on 11/16 modules)
  • --rse:
    • Geomean: 0.871s -> 0.852s (-2.2%)
    • Total time: 26.03s -> 25.39s (-2.5%; faster on 12/16 modules)

Replace the recursive std::variant/Cycle representation of
WeakTopologicalOrdering with a single contiguous std::vector<Entry>
where cycle ends store the header block and jump target index. This
avoids per-cycle vector allocations and recursive evaluation in
WTOWorklist::run.

Benchmark results across 16 WebAssembly modules (3 iterations,
interleaved):
- --constraint-analysis:
  - Geomean: 1.606s -> 1.592s (-0.9%)
  - Total time: 64.05s -> 63.67s (-0.6%; faster on 11/16 modules)
- --rse:
  - Geomean: 0.871s -> 0.852s (-2.2%)
  - Total time: 26.03s -> 25.39s (-2.5%; faster on 12/16 modules)
@tlively
tlively requested a review from kripken October 6, 2026 07:10
@tlively
tlively requested a review from a team as a code owner October 6, 2026 07:10
@kripken

kripken commented Oct 7, 2026

Copy link
Copy Markdown
Member

Before I start to read this, can I ask about the process here? This was part of the initial sequence of PRs iianm. Why was this not applied to the first in the sequence? I mean, why is this a followup and not the initial version?

@tlively

tlively commented Oct 7, 2026

Copy link
Copy Markdown
Member Author

The initial PR was the simplest possible code for the new algorithm, and all the follow-on PRs have been much smaller optimizations on top of that, often increasing complexity in exchange for better performance. This one in particular increases complexity significantly by making the nesting structure of the WTO implicit instead of explicit.

Separating out the follow-on optimizations helps us consider whether they independently carry their weight. For example, there was one optimization I had locally that turned out not to be helpful, so I dropped it before uploading the PRs in this stack.

Comment thread src/cfg/wto.h
struct Entry {
BasicBlock* block = nullptr;
Index cycleTarget = NoTarget;
};

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)

@kripken

kripken commented Oct 8, 2026

Copy link
Copy Markdown
Member

I see, thanks. This doesn't look obviously more complex, though now that I am reading it, I see I don't follow... 😄

Comment thread src/cfg/wto.h
// pointers and a `contents.index` field of type `Index`.
template<typename BasicBlock> struct WeakTopologicalOrdering {
static constexpr Index NoIndex = Index(-1);
static constexpr Index NoTarget = Index(-1);

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 this is obviously redundant!

Comment thread src/cfg/wto.h
// end of the cycle headed by `block` (`cycleTarget` is the entry index of the
// cycle header).
struct Entry {
BasicBlock* block = nullptr;

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
BasicBlock* block = nullptr;
BasicBlock* block;

This made me think it was optional, and I don't see a benefit to this default?

This branch has not been deployed

No deployments
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