From 4016344d34745221f36f37cf763e057da1dc53b6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 22 Sep 2026 16:36:53 -0700 Subject: [PATCH 1/7] work --- src/cfg/rpo.h | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/cfg/rpo.h diff --git a/src/cfg/rpo.h b/src/cfg/rpo.h new file mode 100644 index 00000000000..753cc8fa5c5 --- /dev/null +++ b/src/cfg/rpo.h @@ -0,0 +1,80 @@ +/* + * Copyright 2026 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Utilities for reverse-postorder queue management. +// + +#ifndef rpo_h +#define rpo_h + +#include + +#include "wasm.h" + +namespace wasm { + +// +// Given a CFG in reverse postorder (e.g. from cfg-traversal), implement a priority queue +// working in reverse postorder. BasicBlock indexes indicate the block's position +// in RPO, and by processing the ones with lower indexes first, we can ensure +// that we fully process loops and diamonds before proceeding onward to flow +// data elsewhere in the CFG. This avoids the wasted work problem where we have, +// say, an If, and process one arm, then look at the rest of a massive function, +// then process the other If arm, and the entire massive function must be +// recomputed. +// +// The BasicBlock of the CFG must contain two fields: +// +// bool inQueue; // whether already in the queue +// Index index; // basic block index +// +template +struct RPOQueue : public std::priority_queue, std::greater> { + CFG& cfg; + + RPOQueue(CFG& cfg) : cfg(cfg) { + // Initialize the block indexes and queue booleans. + auto& basicBlocks = cfg.basicBlocks; + for (Index i = 0; i < basicBlocks.size(); ++i) { + auto& contents = basicBlocks[i]->contents; + contents.inQueue = false; + contents.index = i; + } + } + + void push(CFG::BasicBlock* block) { + // Push if ont already in the queue. + if (!block->contents.inQueue) { + block->contents.inQueue = true; + work.push(block->contents.index); + } + } + + CFG::BasicBlock* pop() { + // Pop the smallest element (next in RPO), which is at the top. + auto* block = cfg.basicBlocks[work.top()].get(); + work.pop(); + block->contents.inQueue = false; + return block; + } +}; + +} // namespace wasm + +#endif // rpo_h + +// TODO: use in moar passes From 14a127dcbeb5ff384aff444b231db2e00867299c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 22 Sep 2026 16:37:07 -0700 Subject: [PATCH 2/7] format --- src/cfg/rpo.h | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/cfg/rpo.h b/src/cfg/rpo.h index 753cc8fa5c5..098f88d8d34 100644 --- a/src/cfg/rpo.h +++ b/src/cfg/rpo.h @@ -28,14 +28,14 @@ namespace wasm { // -// Given a CFG in reverse postorder (e.g. from cfg-traversal), implement a priority queue -// working in reverse postorder. BasicBlock indexes indicate the block's position -// in RPO, and by processing the ones with lower indexes first, we can ensure -// that we fully process loops and diamonds before proceeding onward to flow -// data elsewhere in the CFG. This avoids the wasted work problem where we have, -// say, an If, and process one arm, then look at the rest of a massive function, -// then process the other If arm, and the entire massive function must be -// recomputed. +// Given a CFG in reverse postorder (e.g. from cfg-traversal), implement a +// priority queue working in reverse postorder. BasicBlock indexes indicate the +// block's position in RPO, and by processing the ones with lower indexes first, +// we can ensure that we fully process loops and diamonds before proceeding +// onward to flow data elsewhere in the CFG. This avoids the wasted work problem +// where we have, say, an If, and process one arm, then look at the rest of a +// massive function, then process the other If arm, and the entire massive +// function must be recomputed. // // The BasicBlock of the CFG must contain two fields: // @@ -43,9 +43,10 @@ namespace wasm { // Index index; // basic block index // template -struct RPOQueue : public std::priority_queue, std::greater> { +struct RPOQueue + : public std::priority_queue, std::greater> { CFG& cfg; - + RPOQueue(CFG& cfg) : cfg(cfg) { // Initialize the block indexes and queue booleans. auto& basicBlocks = cfg.basicBlocks; From 145b445692cad604c8b372a72e711a31cb6ce73b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 22 Sep 2026 16:44:21 -0700 Subject: [PATCH 3/7] yolo --- src/cfg/rpo.h | 15 ++++++++++----- src/passes/ConstraintAnalysis.cpp | 7 ++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/cfg/rpo.h b/src/cfg/rpo.h index 098f88d8d34..baa7e59f03b 100644 --- a/src/cfg/rpo.h +++ b/src/cfg/rpo.h @@ -43,8 +43,7 @@ namespace wasm { // Index index; // basic block index // template -struct RPOQueue - : public std::priority_queue, std::greater> { +struct RPOQueue { CFG& cfg; RPOQueue(CFG& cfg) : cfg(cfg) { @@ -57,21 +56,27 @@ struct RPOQueue } } + std::priority_queue, std::greater> queue; + void push(CFG::BasicBlock* block) { // Push if ont already in the queue. if (!block->contents.inQueue) { block->contents.inQueue = true; - work.push(block->contents.index); + queue.push(block->contents.index); } } CFG::BasicBlock* pop() { // Pop the smallest element (next in RPO), which is at the top. - auto* block = cfg.basicBlocks[work.top()].get(); - work.pop(); + auto* block = cfg.basicBlocks[queue.top()].get(); + queue.pop(); block->contents.inQueue = false; return block; } + + bool empty() const { + return queue.empty(); + } }; } // namespace wasm diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 9e275665e9e..091e46b3657 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -69,6 +69,7 @@ #include #include "cfg/cfg-traversal.h" +#include "cfg/rpo.h" #include "ir/constraint.h" #include "ir/drop.h" #include "ir/eh-utils.h" @@ -96,6 +97,10 @@ namespace { // Information in a basic block. struct Info { + // For RPOQueue + bool inQueue; + Index index; + // All relevant operations: local gets and sets and uses of them. std::vector actions; @@ -337,7 +342,7 @@ struct ConstraintAnalysis } // Starting from the entry, keep going while we find something new. - UniqueDeferredQueue work; + RPOQueue work(*this); work.push(entry); while (!work.empty()) { From 182f8b34d8e67af02c2db63008c40797c881afce Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 22 Sep 2026 16:44:27 -0700 Subject: [PATCH 4/7] yolo --- src/cfg/rpo.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/cfg/rpo.h b/src/cfg/rpo.h index baa7e59f03b..54efa71f33d 100644 --- a/src/cfg/rpo.h +++ b/src/cfg/rpo.h @@ -42,8 +42,7 @@ namespace wasm { // bool inQueue; // whether already in the queue // Index index; // basic block index // -template -struct RPOQueue { +template struct RPOQueue { CFG& cfg; RPOQueue(CFG& cfg) : cfg(cfg) { @@ -74,9 +73,7 @@ struct RPOQueue { return block; } - bool empty() const { - return queue.empty(); - } + bool empty() const { return queue.empty(); } }; } // namespace wasm From 086afa5b77d6e91cf5b15e3a82970d81240d80ff Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 23 Sep 2026 10:54:57 -0700 Subject: [PATCH 5/7] typo --- src/cfg/rpo.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cfg/rpo.h b/src/cfg/rpo.h index 54efa71f33d..809c5d39685 100644 --- a/src/cfg/rpo.h +++ b/src/cfg/rpo.h @@ -58,7 +58,7 @@ template struct RPOQueue { std::priority_queue, std::greater> queue; void push(CFG::BasicBlock* block) { - // Push if ont already in the queue. + // Push if not already in the queue. if (!block->contents.inQueue) { block->contents.inQueue = true; queue.push(block->contents.index); From 0ffabdff2cb4169655e64ce444bd4121676a1f98 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 23 Sep 2026 14:12:57 -0700 Subject: [PATCH 6/7] rpo liveness-traversal (CoalesceLocals) --- src/cfg/liveness-traversal.h | 17 ++++++++++------- src/cfg/rpo.h | 17 ++++++++++++----- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/cfg/liveness-traversal.h b/src/cfg/liveness-traversal.h index 49e8532220c..7bae2cbaf48 100644 --- a/src/cfg/liveness-traversal.h +++ b/src/cfg/liveness-traversal.h @@ -23,6 +23,7 @@ #include "cfg-traversal.h" #include "ir/utils.h" +#include "rpo.h" #include "support/sorted_vector.h" #include "support/sparse_square_matrix.h" #include "wasm-builder.h" @@ -82,6 +83,10 @@ struct LivenessAction { // information about liveness in a basic block struct Liveness { + // For POQueue + bool inQueue; + Index index; + SetOfLocals start, end; // live locals at the start and end std::vector actions; // actions occurring in this block @@ -220,12 +225,12 @@ struct LivenessWalker : public CFGWalker { void flowLiveness() { // keep working while stuff is flowing - std::unordered_set queue; + POQueue> queue(*this); for (auto& curr : CFGWalker::basicBlocks) { if (!liveBlocks.contains(curr.get())) { continue; // ignore dead blocks } - queue.insert(curr.get()); + queue.push(curr.get()); // do the first scan through the block, starting with nothing live at the // end, and updating the liveness at the start scanLivenessThroughActions(curr->contents.actions, curr->contents.start); @@ -233,10 +238,8 @@ struct LivenessWalker : public CFGWalker { // at every point in time, we assume we already noted interferences between // things already known alive at the end, and scanned back through the block // using that - while (queue.size() > 0) { - auto iter = queue.begin(); - auto* curr = *iter; - queue.erase(iter); + while (!queue.empty()) { + auto* curr = queue.pop(); SetOfLocals live; if (!mergeStartsAndCheckChange(curr->out, curr->contents.end, live)) { continue; @@ -252,7 +255,7 @@ struct LivenessWalker : public CFGWalker { assert(curr->contents.start.size() < live.size()); curr->contents.start = live; for (auto* in : curr->in) { - queue.insert(in); + queue.push(in); } } } diff --git a/src/cfg/rpo.h b/src/cfg/rpo.h index 809c5d39685..e3af10ab7f5 100644 --- a/src/cfg/rpo.h +++ b/src/cfg/rpo.h @@ -42,7 +42,7 @@ namespace wasm { // bool inQueue; // whether already in the queue // Index index; // basic block index // -template struct RPOQueue { +template> struct RPOQueue { CFG& cfg; RPOQueue(CFG& cfg) : cfg(cfg) { @@ -55,9 +55,9 @@ template struct RPOQueue { } } - std::priority_queue, std::greater> queue; + std::priority_queue, Compare> queue; - void push(CFG::BasicBlock* block) { + void push(typename CFG::BasicBlock* block) { // Push if not already in the queue. if (!block->contents.inQueue) { block->contents.inQueue = true; @@ -65,8 +65,8 @@ template struct RPOQueue { } } - CFG::BasicBlock* pop() { - // Pop the smallest element (next in RPO), which is at the top. + typename CFG::BasicBlock* pop() { + // Pop the top element. auto* block = cfg.basicBlocks[queue.top()].get(); queue.pop(); block->contents.inQueue = false; @@ -76,6 +76,13 @@ template struct RPOQueue { bool empty() const { return queue.empty(); } }; +// A queue that works in postorder (the reverse of RPO), which is useful when +// flowing information backwards through the CFG (such as in liveness analysis). +// By processing blocks with higher RPO indexes first, we process successors +// before predecessors, and fully process loops and diamonds before flowing data +// backwards to earlier blocks in the CFG. +template using POQueue = RPOQueue>; + } // namespace wasm #endif // rpo_h From e780f9d974e4627d2ce1316c6aeecbd38c134e3f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 23 Sep 2026 17:04:38 -0700 Subject: [PATCH 7/7] feedback --- src/cfg/rpo.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/cfg/rpo.h b/src/cfg/rpo.h index 809c5d39685..f99f5543ac3 100644 --- a/src/cfg/rpo.h +++ b/src/cfg/rpo.h @@ -18,8 +18,8 @@ // Utilities for reverse-postorder queue management. // -#ifndef rpo_h -#define rpo_h +#ifndef cfg_rpo_h +#define cfg_rpo_h #include @@ -78,6 +78,5 @@ template struct RPOQueue { } // namespace wasm -#endif // rpo_h +#endif // cfg_rpo_h -// TODO: use in moar passes